From c870fc7ba7e1f9e65834f3a9264863619f4cfe4e Mon Sep 17 00:00:00 2001 From: Stanislav Kem Date: Sun, 3 Sep 2023 16:03:29 +0200 Subject: [PATCH] updated x509 library --- .github/workflows/test.yml | 10 +- cmd/easyrsa/go.mod | 6 +- cmd/easyrsa/go.sum | 1 + go.mod | 4 +- go.sum | 4 +- internal/compilantStorage/index.go | 107 -------- internal/compilantStorage/index_test.go | 228 ------------------ internal/compilantStorage/storage.go | 124 ---------- internal/compilantStorage/storage_test.go | 77 ------ internal/compilantStorage/test/.gitignore | 1 - internal/fsStorage/storage.go | 16 +- internal/fsStorage/storage_test.go | 6 +- .../fsStorage/test/dir_keystorage/exist.pem | 1 + internal/utils/utils.go | 7 +- pkg/pki/pki.go | 57 ++++- pkg/pki/pki_test.go | 10 +- pkg/pki/struct.go | 8 +- 17 files changed, 87 insertions(+), 580 deletions(-) delete mode 100644 internal/compilantStorage/index.go delete mode 100644 internal/compilantStorage/index_test.go delete mode 100644 internal/compilantStorage/storage.go delete mode 100644 internal/compilantStorage/storage_test.go delete mode 100644 internal/compilantStorage/test/.gitignore create mode 100644 internal/fsStorage/test/dir_keystorage/exist.pem diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6f2166a..6bb8029 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ jobs: test: strategy: matrix: - go-version: [1.18.x] + go-version: [1.21.x] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: @@ -25,7 +25,7 @@ jobs: steps: - uses: actions/setup-go@v3 with: - go-version: 1.18 + go-version: 1.21 - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3 @@ -38,16 +38,16 @@ jobs: if: success() uses: actions/setup-go@v2 with: - go-version: 1.18.x + go-version: 1.21.x - name: Checkout code uses: actions/checkout@v2 - name: Calc coverage run: | go test -v ./... -covermode=count -coverprofile=coverage.out - name: Convert coverage.out to coverage.lcov - uses: jandelgado/gcov2lcov-action@v1.0.6 + uses: jandelgado/gcov2lcov-action@v1.0.9 - name: Coveralls - uses: coverallsapp/github-action@v1.1.2 + uses: coverallsapp/github-action@v2.2.1 with: github-token: ${{ secrets.github_token }} path-to-lcov: coverage.lcov diff --git a/cmd/easyrsa/go.mod b/cmd/easyrsa/go.mod index 94b12d1..85c2c92 100644 --- a/cmd/easyrsa/go.mod +++ b/cmd/easyrsa/go.mod @@ -1,6 +1,8 @@ module github.com/kemsta/go-easyrsa/cmd/easyrsa -go 1.18 +go 1.21 + +toolchain go1.21.0 replace github.com/kemsta/go-easyrsa => ../../ @@ -13,5 +15,5 @@ require ( github.com/gofrs/flock v0.8.1 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect + golang.org/x/sys v0.12.0 // indirect ) diff --git a/cmd/easyrsa/go.sum b/cmd/easyrsa/go.sum index 1aaa80f..f0147e5 100644 --- a/cmd/easyrsa/go.sum +++ b/cmd/easyrsa/go.sum @@ -15,6 +15,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/go.mod b/go.mod index 1491a47..398e34f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/kemsta/go-easyrsa -go 1.18 +go 1.21 require ( github.com/gofrs/flock v0.8.1 @@ -12,7 +12,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect + golang.org/x/sys v0.12.0 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 81bfb79..ff3c4f4 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +15,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/compilantStorage/index.go b/internal/compilantStorage/index.go deleted file mode 100644 index 003e4d0..0000000 --- a/internal/compilantStorage/index.go +++ /dev/null @@ -1,107 +0,0 @@ -package compilantStorage - -import ( - "bufio" - "fmt" - "io" - "strings" - "time" - "unicode/utf8" -) - -const dateLayout = "060102150405Z" - -type Index struct { - records []Record -} - -//https://pki-tutorial.readthedocs.io/en/latest/cadb.html -//https://www.openssl.org/docs/man1.0.2/man1/openssl-ca.html - -type Record struct { - statusFlag rune //Certificate status flag (V=valid, R=revoked, E=expired) - expirationDate *time.Time //Certificate expiration date - revocationDate *time.Time //Certificate revocation date, empty if not revoked - revocationReason string //Certificate revocation reason if presented - certSerialHex string //Certificate serial number in hex - certFileName string //Certificate filename or literal string ‘unknown’ - certDN string //Certificate distinguished name -} - -func (r Record) String() string { - var revString string - if r.revocationDate != nil { - revString = r.revocationDate.Format(dateLayout) - if r.revocationReason != "" { - revString = fmt.Sprintf("%v,%v", r.revocationDate.Format(dateLayout), r.revocationReason) - } - } - - return fmt.Sprintf("%v\t%v\t%v\t%v\t%v\t%v", string(r.statusFlag), r.expirationDate.Format(dateLayout), revString, - r.certSerialHex, r.certFileName, r.certDN) -} - -func (i *Index) Len() int { - return len(i.records) -} - -func (i *Index) Decode(r io.Reader) error { - br := bufio.NewReader(r) - for { - line, _, err := br.ReadLine() - if err != nil { - if err == io.EOF { - break - } - return fmt.Errorf("couldn't read line from index: %w", err) - } - - record, err := parseLine(line) - if err != nil { - return fmt.Errorf("couldn't parse record from index: %w", err) - } - i.records = append(i.records, *record) - } - return nil -} - -func parseLine(line []byte) (*Record, error) { - split := strings.Split(string(line), "\t") - if len(split) != 6 { - return nil, fmt.Errorf("wrong records format: %v", string(line)) - } - rec := new(Record) - rec.statusFlag, _ = utf8.DecodeRuneInString(split[0]) - parsedDate, err := time.Parse(dateLayout, split[1]) - if err != nil { - return nil, fmt.Errorf("couldn't parse date from %v : %w", split[1], err) - } - rec.expirationDate = &parsedDate - if split[2] != "" { - revoc := strings.Split(split[2], ",") - parsedDate, err = time.Parse(dateLayout, revoc[0]) - if err != nil { - return nil, fmt.Errorf("couldn't parse date from %v : %w", split[2], err) - } - rec.revocationDate = &parsedDate - if len(revoc) == 2 { - rec.revocationReason = revoc[1] - } - } - - rec.certSerialHex = split[3] - rec.certFileName = split[4] - rec.certDN = split[5] - - return rec, nil -} - -func (i *Index) Encode(w io.Writer) error { - for _, r := range i.records { - _, err := w.Write([]byte(r.String())) - if err != nil { - return fmt.Errorf("couldn't write encoded index: %w", err) - } - } - return nil -} diff --git a/internal/compilantStorage/index_test.go b/internal/compilantStorage/index_test.go deleted file mode 100644 index 7e2317f..0000000 --- a/internal/compilantStorage/index_test.go +++ /dev/null @@ -1,228 +0,0 @@ -package compilantStorage - -import ( - "bytes" - "fmt" - "github.com/stretchr/testify/assert" - "io" - "strings" - "testing" - "time" -) - -type fakeReader struct { -} - -func (f fakeReader) Read(p []byte) (n int, err error) { - return 0, io.ErrClosedPipe -} - -type fakeWriter struct { -} - -func (f fakeWriter) Write(p []byte) (n int, err error) { - return 0, io.ErrClosedPipe -} - -func TestIndex_Decode(t *testing.T) { - type args struct { - r io.Reader - } - tests := []struct { - name string - i *Index - args args - wantErr bool - funcV func(index *Index, t *testing.T) bool - }{ - { - name: "mt", - i: new(Index), - args: args{ - r: strings.NewReader(""), - }, - wantErr: false, - funcV: func(index *Index, t *testing.T) bool { - return true - }, - }, - { - name: "oneline", - i: new(Index), - args: args{ - r: strings.NewReader("V\t240830094439Z\t\tA687897D709E441C85A0B2EF9C02C80D\tunknown\t/CN=test1"), - }, - wantErr: false, - funcV: func(index *Index, t *testing.T) bool { - return assert.Equal(t, 1, index.Len()) - }, - }, - { - name: "multiline", - i: new(Index), - args: args{ - r: strings.NewReader("V\t240830094439Z\t\tA687897D709E441C85A0B2EF9C02C80D\tunknown\t/CN=test1\nR\t240831190001Z\t220529195720Z\tB2B9D80AE52F4E739FB1A4D696417D30\tunknown\t/CN=client\nR\t240831190253Z\t220618182903Z,keyCompromise\tCBF3370F0AB460655DF6FA60FFCA421F\tunknown\t/CN=client2\nV\t240831190819Z\t\tC3B12A550081FB41EF0F67C3678EA4BC\tunknown\t/CN=server\n"), - }, - wantErr: false, - funcV: func(index *Index, t *testing.T) bool { - return assert.Equal(t, 4, index.Len()) - }, - }, - { - name: "fakereader", - i: new(Index), - args: args{ - r: new(fakeReader), - }, - wantErr: true, - funcV: func(index *Index, t *testing.T) bool { - return true - }, - }, - { - name: "brokenrecord", - i: new(Index), - args: args{ - r: strings.NewReader("V\t240830094439Z\t\tA687897D709E441C85A0B2EF9C02C80D\tunknown"), - }, - wantErr: true, - funcV: func(index *Index, t *testing.T) bool { - return true - }, - }, - { - name: "wrong exp date", - i: new(Index), - args: args{ - r: strings.NewReader("R\t241331190253Z\t220630182903Z,keyCompromise\tCBF3370F0AB460655DF6FA60FFCA421F\tunknown\t/CN=client2"), - }, - wantErr: true, - funcV: func(index *Index, t *testing.T) bool { - return true - }, - }, - { - name: "wrong revoc date", - i: new(Index), - args: args{ - r: strings.NewReader("R\t240831190253Z\t220632182903Z,keyCompromise\tCBF3370F0AB460655DF6FA60FFCA421F\tunknown\t/CN=client2"), - }, - wantErr: true, - funcV: func(index *Index, t *testing.T) bool { - return true - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.i.Decode(tt.args.r) - if (err != nil) != tt.wantErr { - t.Errorf("Decode() error = %v, wantErr %v", err, tt.wantErr) - } else { - tt.funcV(tt.i, t) - } - }) - } -} - -func TestIndex_Encode(t *testing.T) { - dt := time.Date(2020, 01, 06, 12, 24, 24, 00, time.UTC) - type fields struct { - records []Record - } - tests := []struct { - name string - fields fields - wantW string - wantErr assert.ErrorAssertionFunc - writer io.Writer - }{ - { - name: "mt", - fields: fields{}, - wantW: "", - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return true - }, - writer: nil, - }, - { - name: "good", - fields: fields{ - records: []Record{ - { - statusFlag: 86, - expirationDate: &dt, - revocationDate: nil, - revocationReason: "", - certSerialHex: "AB12", - certFileName: "unknown", - certDN: "/CN=client3", - }, - { - statusFlag: 86, - expirationDate: &dt, - revocationDate: &dt, - revocationReason: "keyCompromise", - certSerialHex: "AB12", - certFileName: "unknown", - certDN: "/CN=client3", - }, - }, - }, - wantW: "V\t200106122424Z\t\tAB12\tunknown\t/CN=client3V\t200106122424Z\t200106122424Z,keyCompromise\tAB12\tunknown\t/CN=client3", - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.NoError(t, err) - }, - }, - { - name: "good", - fields: fields{ - records: []Record{ - { - statusFlag: 86, - expirationDate: &dt, - revocationDate: nil, - revocationReason: "", - certSerialHex: "AB12", - certFileName: "unknown", - certDN: "/CN=client3", - }, - { - statusFlag: 86, - expirationDate: &dt, - revocationDate: &dt, - revocationReason: "keyCompromise", - certSerialHex: "AB12", - certFileName: "unknown", - certDN: "/CN=client3", - }, - }, - }, - wantW: "", - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.Error(t, err) - }, - writer: fakeWriter{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var err error - i := &Index{ - records: tt.fields.records, - } - w := &bytes.Buffer{} - if tt.writer != nil { - err = i.Encode(tt.writer) - } else { - err = i.Encode(w) - } - - if !tt.wantErr(t, err, fmt.Sprintf("Encode(%v)", w)) { - return - } - assert.Equalf(t, tt.wantW, w.String(), "Encode(%v)", w) - }) - } -} diff --git a/internal/compilantStorage/storage.go b/internal/compilantStorage/storage.go deleted file mode 100644 index 4fc8dd8..0000000 --- a/internal/compilantStorage/storage.go +++ /dev/null @@ -1,124 +0,0 @@ -package compilantStorage - -import ( - "bytes" - "fmt" - "github.com/kemsta/go-easyrsa/internal/utils" - "github.com/kemsta/go-easyrsa/pkg/pair" - "io/ioutil" - "math/big" - "os" - "path/filepath" - "strings" - "sync" - "time" -) - -const ( - LockPeriod = time.Millisecond * 100 - LockTimeout = time.Second * 10 - CertFileExtension = ".crt" // certificate file extension -) - -//DirKeyStorage is easyrsa v3 compilant storage. It can be used as a drop-off replacement on the created with easyrsa v3 pki -type DirKeyStorage struct { - pkidir string -} - -func NewDirKeyStorage(pkidir string) *DirKeyStorage { - return &DirKeyStorage{pkidir: pkidir} -} - -func (s *DirKeyStorage) initDir() error { - var once sync.Once - var err error - once.Do(func() { - for _, dir := range []string{ - s.pkidir, - filepath.Join(s.pkidir, "certs_by_serial"), - filepath.Join(s.pkidir, "issued"), - filepath.Join(s.pkidir, "private"), - filepath.Join(s.pkidir, "reqs"), - filepath.Join(s.pkidir, "revoked"), - filepath.Join(s.pkidir, "revoked", "certs_by_serial"), - filepath.Join(s.pkidir, "revoked", "private_by_serial"), - filepath.Join(s.pkidir, "revoked", "reqs_by_serial"), - } { - err = os.MkdirAll(dir, 0750) - } - }) - return err -} - -func (s *DirKeyStorage) Put(pair *pair.X509Pair) error { - err := s.initDir() - if err != nil { - return fmt.Errorf("can`t make pki paths in %s: %w", s.pkidir, err) - } - - var certPath, keyPath, serialPath string - - _, err = os.Stat(certPath) - if err != nil { - if !os.IsNotExist(err) { - return fmt.Errorf("%s already exist. Abort writing to avoid overwriting this file", certPath) - } - } - - certPath = filepath.Join(s.pkidir, "issued", fmt.Sprintf("%s.crt", pair.CN())) - keyPath = filepath.Join(s.pkidir, "private", fmt.Sprintf("%s.key", pair.CN())) - serialPath = filepath.Join(s.pkidir, "certs_by_serial", fmt.Sprintf("%s.crt", strings.ToUpper(pair.Serial().Text(16)))) - if pair.CN() == "ca" { - certPath = filepath.Join(s.pkidir, "ca.crt") - } - if err := utils.WriteFileAtomic(certPath, bytes.NewReader(pair.CertPemBytes()), 0644); err != nil { - return fmt.Errorf("can`t write cert %v: %w", certPath, err) - } - if err := utils.WriteFileAtomic(serialPath, bytes.NewReader(pair.CertPemBytes()), 0644); err != nil { - return fmt.Errorf("can`t write cert %v: %w", certPath, err) - } - - if err := utils.WriteFileAtomic(keyPath, bytes.NewReader(pair.KeyPemBytes()), 0644); err != nil { - return fmt.Errorf("can`t write key %v: %w", keyPath, err) - } - return nil -} - -func (s *DirKeyStorage) GetByCN(cn string) ([]*pair.X509Pair, error) { - res := make([]*pair.X509Pair, 0) - certBytes, err := ioutil.ReadFile(filepath.Join(s.pkidir, "issued", fmt.Sprintf("%s.crt", cn))) - if err != nil { - return nil, fmt.Errorf("can't read cert by cn %s: %w", cn, err) - } - keyBytes, err := ioutil.ReadFile(filepath.Join(s.pkidir, "private", fmt.Sprintf("%s.key", cn))) - if err != nil { - return nil, fmt.Errorf("can't read key by cn %s: %w", cn, err) - } - res = append(res, pair.ImportX509(keyBytes, certBytes, cn, big.NewInt(0))) - return res, err -} - -func (s *DirKeyStorage) GetLastByCn(cn string) (*pair.X509Pair, error) { - //TODO implement me - panic("implement me") -} - -func (s *DirKeyStorage) GetBySerial(serial *big.Int) (*pair.X509Pair, error) { - //TODO implement me - panic("implement me") -} - -func (s *DirKeyStorage) DeleteByCN(cn string) error { - //TODO implement me - panic("implement me") -} - -func (s *DirKeyStorage) DeleteBySerial(serial *big.Int) error { - //TODO implement me - panic("implement me") -} - -func (s *DirKeyStorage) GetAll() ([]*pair.X509Pair, error) { - //TODO implement me - panic("implement me") -} diff --git a/internal/compilantStorage/storage_test.go b/internal/compilantStorage/storage_test.go deleted file mode 100644 index c22c8ed..0000000 --- a/internal/compilantStorage/storage_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package compilantStorage - -import ( - "bytes" - "github.com/kemsta/go-easyrsa/pkg/pair" - "io/ioutil" - "math/big" - "path/filepath" - "testing" -) - -func getTestDir() string { - res, _ := filepath.Abs("test") - return res -} - -func TestDirKeyStorage_Put(t *testing.T) { - type fields struct { - keydir string - } - type args struct { - pair *pair.X509Pair - } - tests := []struct { - name string - fields fields - args args - wantErr bool - }{ - { - name: "good", - fields: fields{ - keydir: filepath.Join(getTestDir(), "dir_keystorage"), - }, - args: args{ - pair: pair.ImportX509([]byte("keybytes"), []byte("certbytes"), "good_cert", big.NewInt(66)), - }, - wantErr: false, - }, - { - name: "ca", - fields: fields{ - keydir: filepath.Join(getTestDir(), "dir_keystorage"), - }, - args: args{ - pair: pair.ImportX509([]byte("keybytes"), []byte("certbytes"), "ca", big.NewInt(154)), - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &DirKeyStorage{ - pkidir: tt.fields.keydir, - } - if err := s.Put(tt.args.pair); (err != nil) != tt.wantErr { - t.Errorf("DirKeyStorage.Put() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } - certBytes, _ := ioutil.ReadFile(filepath.Join(getTestDir(), "dir_keystorage", "issued/good_cert.crt")) - if !bytes.Equal(certBytes, []byte("certbytes")) { - t.Errorf("DirKeyStorage.Put() wrong cert bytes in result file") - } - certBytes, _ = ioutil.ReadFile(filepath.Join(getTestDir(), "dir_keystorage", "certs_by_serial/9A.crt")) - if !bytes.Equal(certBytes, []byte("certbytes")) { - t.Errorf("DirKeyStorage.Put() wrong cert bytes in result file") - } - certBytes, _ = ioutil.ReadFile(filepath.Join(getTestDir(), "dir_keystorage", "ca.crt")) - if !bytes.Equal(certBytes, []byte("certbytes")) { - t.Errorf("DirKeyStorage.Put() wrong cert bytes in result file") - } - keyBytes, _ := ioutil.ReadFile(filepath.Join(getTestDir(), "dir_keystorage", "private/good_cert.key")) - if !bytes.Equal(keyBytes, []byte("keybytes")) { - t.Errorf("DirKeyStorage.Put() wrong key bytes in result file") - } -} diff --git a/internal/compilantStorage/test/.gitignore b/internal/compilantStorage/test/.gitignore deleted file mode 100644 index 63f1fef..0000000 --- a/internal/compilantStorage/test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.lock diff --git a/internal/fsStorage/storage.go b/internal/fsStorage/storage.go index d22eeb6..a0bba3c 100644 --- a/internal/fsStorage/storage.go +++ b/internal/fsStorage/storage.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "crypto/x509" - "crypto/x509/pkix" + "encoding/pem" "errors" "fmt" "github.com/gofrs/flock" @@ -26,6 +26,8 @@ const ( CertFileExtension = ".crt" // certificate file extension ) +var ErrorCrlNotExist = errors.New("crl is not exist") + // FileCRLHolder is a common CRLHolder implementation. It's saving file on fs type FileCRLHolder struct { locker *flock.Flock @@ -58,7 +60,7 @@ func (h *FileCRLHolder) Put(content []byte) error { } // Get crl content from the storage -func (h *FileCRLHolder) Get() (*pkix.CertificateList, error) { +func (h *FileCRLHolder) Get() (*x509.RevocationList, error) { err := h.locker.RLock() if err != nil { return nil, err @@ -67,13 +69,17 @@ func (h *FileCRLHolder) Get() (*pkix.CertificateList, error) { _ = h.locker.Unlock() }() if stat, err := os.Stat(h.path); err != nil || stat.Size() == 0 { - return &pkix.CertificateList{}, nil + return nil, ErrorCrlNotExist } - fBytes, err := ioutil.ReadFile(h.path) + fBytes, err := os.ReadFile(h.path) if err != nil { return nil, fmt.Errorf("can`t read crl %v: %w", h.path, err) } - list, err := x509.ParseCRL(fBytes) + crlPemBlock, _ := pem.Decode(fBytes) + if crlPemBlock == nil { + return nil, errors.New("empty crl or mailformed") + } + list, err := x509.ParseRevocationList(crlPemBlock.Bytes) if err != nil { return nil, fmt.Errorf("can`t parse crl \n %v: %w", string(fBytes), err) } diff --git a/internal/fsStorage/storage_test.go b/internal/fsStorage/storage_test.go index 35e9c66..88f41c1 100644 --- a/internal/fsStorage/storage_test.go +++ b/internal/fsStorage/storage_test.go @@ -2,7 +2,7 @@ package fsStorage import ( "bytes" - "crypto/x509/pkix" + "crypto/x509" "fmt" "github.com/kemsta/go-easyrsa/internal/utils" "github.com/kemsta/go-easyrsa/pkg/pair" @@ -505,7 +505,7 @@ func TestFileCRLHolder_Get(t *testing.T) { tests := []struct { name string fields fields - want *pkix.CertificateList + want *x509.RevocationList wantErr bool }{ { @@ -514,7 +514,7 @@ func TestFileCRLHolder_Get(t *testing.T) { path: filepath.Join(getTestDir(), "dir_keystorage", "not_exist"), }, want: nil, - wantErr: false, + wantErr: true, }, { name: "broken", diff --git a/internal/fsStorage/test/dir_keystorage/exist.pem b/internal/fsStorage/test/dir_keystorage/exist.pem new file mode 100644 index 0000000..1050001 --- /dev/null +++ b/internal/fsStorage/test/dir_keystorage/exist.pem @@ -0,0 +1 @@ +asd \ No newline at end of file diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 0c4f95b..b187795 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -3,7 +3,6 @@ package utils import ( "fmt" "io" - "io/ioutil" "os" "path/filepath" ) @@ -13,7 +12,7 @@ func WriteFileAtomic(path string, r io.Reader, mode os.FileMode) error { if dir == "" { dir = "." } - fd, err := ioutil.TempFile(dir, file) + fd, err := os.CreateTemp(dir, file) if err != nil { return fmt.Errorf("cannot create temp file: %w", err) } @@ -27,10 +26,10 @@ func WriteFileAtomic(path string, r io.Reader, mode os.FileMode) error { return fmt.Errorf("cannot write data to tempfile %q: %w", fd.Name(), err) } if err := fd.Sync(); err != nil { - return fmt.Errorf("can't flush tempfile %q: %v", fd.Name(), err) + return fmt.Errorf("can't flush tempfile %q: %w", fd.Name(), err) } if err := fd.Close(); err != nil { - return fmt.Errorf("can't close tempfile %q: %v", fd.Name(), err) + return fmt.Errorf("can't close tempfile %q: %w", fd.Name(), err) } if err := os.Chmod(fd.Name(), mode); err != nil { return fmt.Errorf("can't set filemode on tempfile %q: %w", fd.Name(), err) diff --git a/pkg/pki/pki.go b/pkg/pki/pki.go index bd04692..47202af 100644 --- a/pkg/pki/pki.go +++ b/pkg/pki/pki.go @@ -190,8 +190,23 @@ func (p *PKI) NewCert(keySizeBytes int, selfsigned bool, opts ...CertificateOpti } // GetCRL return current revoke list -func (p *PKI) GetCRL() (*pkix.CertificateList, error) { - return p.crlHolder.Get() +func (p *PKI) GetCRL() (crl *x509.RevocationList, err error) { + crl, err = p.crlHolder.Get() + if err != nil { + if !errors.Is(err, fsStorage.ErrorCrlNotExist) { + return nil, fmt.Errorf("couldn't get old crl") + } + crlBytes, err := p.newCrl() + if err != nil { + return nil, fmt.Errorf("couldn't create new crl") + } + crl, err = x509.ParseRevocationList(crlBytes) + if err != nil { + return nil, fmt.Errorf("couldn't create new crl") + } + } + + return crl, nil } // GetLastCA return last CA pair @@ -199,11 +214,30 @@ func (p *PKI) GetLastCA() (*pair.X509Pair, error) { return p.storage.GetLastByCn("ca") } +func (p *PKI) newCrl() ([]byte, error) { + caPairs, err := p.storage.GetByCN("ca") + if err != nil { + return nil, fmt.Errorf("can`t get ca certs for signing crl: %w", err) + } + caKey, caCert, err := caPairs[0].Decode() + if err != nil { + return nil, fmt.Errorf("can`t decode ca certs for signing crl: %w", err) + } + template := &x509.RevocationList{ + Number: big.NewInt(1), + ThisUpdate: time.Now(), + NextUpdate: time.Now(), + } + return x509.CreateRevocationList(rand.Reader, template, caCert, caKey) +} + // RevokeOne revoke one pair with serial -func (p *PKI) RevokeOne(serial *big.Int) error { - list := make([]pkix.RevokedCertificate, 0) - if oldList, err := p.GetCRL(); err == nil { - list = oldList.TBSCertList.RevokedCertificates +func (p *PKI) RevokeOne(serial *big.Int) (err error) { + var oldList *x509.RevocationList + if oldList, err = p.GetCRL(); err != nil { + if err != nil { + return fmt.Errorf("couldn't get old crl") + } } caPairs, err := p.storage.GetByCN("ca") if err != nil { @@ -216,12 +250,13 @@ func (p *PKI) RevokeOne(serial *big.Int) error { if err != nil { return fmt.Errorf("can`t decode ca certs for signing crl: %w", err) } - list = append(list, pkix.RevokedCertificate{ + oldList.RevokedCertificateEntries = append(oldList.RevokedCertificateEntries, x509.RevocationListEntry{ SerialNumber: serial, RevocationTime: time.Now(), }) - crlBytes, err := caCert.CreateCRL( - rand.Reader, caKey, removeDups(list), time.Now(), time.Now().Add(DefaultExpireYears*365*24*time.Hour)) + + crlBytes, err := x509.CreateRevocationList( + rand.Reader, oldList, caCert, caKey) if err != nil { return fmt.Errorf("can`t create crl: %w", err) } @@ -255,9 +290,9 @@ func (p *PKI) RevokeAllByCN(cn string) error { func (p *PKI) IsRevoked(serial *big.Int) bool { revokedCerts, err := p.GetCRL() if err != nil { - revokedCerts = &pkix.CertificateList{} + revokedCerts = &x509.RevocationList{} } - for _, cert := range revokedCerts.TBSCertList.RevokedCertificates { + for _, cert := range revokedCerts.RevokedCertificateEntries { if cert.SerialNumber.Cmp(serial) == 0 { return true } diff --git a/pkg/pki/pki_test.go b/pkg/pki/pki_test.go index af1318c..3c0e467 100644 --- a/pkg/pki/pki_test.go +++ b/pkg/pki/pki_test.go @@ -108,8 +108,8 @@ func TestPKI_getCRL(t *testing.T) { defer cleanup() t.Run("get crl", func(t *testing.T) { list, err := pki.GetCRL() - assert.NoError(t, err) - assert.NotNil(t, list) + assert.Error(t, err) + assert.Nil(t, list) }) } @@ -124,7 +124,7 @@ func TestPKI_RevokeOne(t *testing.T) { err := pki.RevokeOne(big.NewInt(300)) assert.NoError(t, err) list, _ := pki.GetCRL() - assert.Equal(t, list.TBSCertList.RevokedCertificates[0].SerialNumber, big.NewInt(300)) + assert.Equal(t, list.RevokedCertificateEntries[0].SerialNumber, big.NewInt(300)) }) } @@ -155,8 +155,8 @@ func TestPKI_RevokeAllByCN(t *testing.T) { err := pki.RevokeAllByCN("server") assert.NoError(t, err) list, _ := pki.GetCRL() - assert.Len(t, list.TBSCertList.RevokedCertificates, 2) - assert.Equal(t, list.TBSCertList.RevokedCertificates[0].SerialNumber, big.NewInt(2)) + assert.Len(t, list.RevokedCertificateEntries, 2) + assert.Equal(t, list.RevokedCertificateEntries[0].SerialNumber, big.NewInt(2)) }) } diff --git a/pkg/pki/struct.go b/pkg/pki/struct.go index 43c939c..5fec8bd 100644 --- a/pkg/pki/struct.go +++ b/pkg/pki/struct.go @@ -1,14 +1,14 @@ package pki import ( - "crypto/x509/pkix" + "crypto/x509" "github.com/kemsta/go-easyrsa/pkg/pair" "math/big" ) // KeyStorage storage interface type KeyStorage interface { - Put(pair *pair.X509Pair) error // Put new pair to KeyStorage. Overwrite if already exist. + Put(pair *pair.X509Pair) error // Put new pair to KeyStorage. GetByCN(cn string) ([]*pair.X509Pair, error) // Get all keypairs by CN. GetLastByCn(cn string) (*pair.X509Pair, error) // Get last pair by CN. GetBySerial(serial *big.Int) (*pair.X509Pair, error) // Get one keypair by serial. @@ -24,6 +24,6 @@ type SerialProvider interface { // CRLHolder is a certificate revocation list holder interface type CRLHolder interface { - Put([]byte) error // Put file content for crl - Get() (*pkix.CertificateList, error) // Get current revoked cert list + Put([]byte) error // Put file content for crl + Get() (*x509.RevocationList, error) // Get current revoked cert list }