Skip to content

Commit

Permalink
Abstract keystore operations
Browse files Browse the repository at this point in the history
Co-authored-by: Johannes Dillmann <j.dillmann@sap.com>
  • Loading branch information
modulo11 committed Nov 7, 2023
1 parent f24597b commit 6a9b8a0
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 54 deletions.
60 changes: 6 additions & 54 deletions certificate_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,12 @@ import (
"encoding/pem"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"time"

"github.com/paketo-buildpacks/libpak/sherpa"
"github.com/pavlo-v-chernykh/keystore-go/v4"
"golang.org/x/sys/unix"
)

const DefaultCertFile = "/etc/ssl/certs/ca-certificates.crt"
Expand Down Expand Up @@ -58,9 +55,9 @@ func NewCertificateLoader() CertificateLoader {
}

func (c *CertificateLoader) Load(path string, password string) error {
ks, err := c.readKeyStore(path, password)
ks, err := DetectKeystore(path)
if err != nil {
return fmt.Errorf("unable to read keystore\n%w", err)
return err
}

files, err := c.certFiles()
Expand All @@ -76,25 +73,14 @@ func (c *CertificateLoader) Load(path string, password string) error {
}

for i, b := range blocks {
entry := keystore.TrustedCertificateEntry{
CreationTime: NormalizedDateTime,
Certificate: keystore.Certificate{
Type: "X.509",
Content: b.Bytes,
},
}

if err := ks.SetTrustedCertificateEntry(fmt.Sprintf("%s-%d", f, i), entry); err != nil {
return fmt.Errorf("unable to add trusted entry\n%w", err)
}

ks.Add(fmt.Sprintf("%s-%d", f, i), b)
added++
}
}

_, _ = fmt.Fprintf(c.Logger, "Adding %d container CA certificates to JVM truststore\n", added)

if err := c.writeKeyStore(ks, path, password); err != nil {
if err := ks.Write(); err != nil {
return fmt.Errorf("unable to write keystore\n%w", err)
}

Expand All @@ -112,7 +98,7 @@ func (c CertificateLoader) certFiles() ([]string, error) {

re := regexp.MustCompile(`^[[:xdigit:]]{8}\.[\d]$`)
for _, d := range c.CertDirs {
c, err := ioutil.ReadDir(d)
c, err := os.ReadDir(d)
if os.IsNotExist(err) {
continue
} else if err != nil {
Expand Down Expand Up @@ -162,7 +148,7 @@ func (c CertificateLoader) readBlocks(path string) ([]*pem.Block, error) {
blocks []*pem.Block
)

rest, err := ioutil.ReadFile(path)
rest, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("unable to read %s\n%w", path, err)
}
Expand All @@ -177,37 +163,3 @@ func (c CertificateLoader) readBlocks(path string) ([]*pem.Block, error) {

return blocks, nil
}

func (CertificateLoader) readKeyStore(path string, password string) (keystore.KeyStore, error) {
in, err := os.Open(path)
if err != nil {
return keystore.KeyStore{}, fmt.Errorf("unable to open %s\n%w", path, err)
}
defer in.Close()

ks := keystore.New(keystore.WithOrderedAliases())
if err := ks.Load(in, []byte(password)); err != nil {
return keystore.KeyStore{}, fmt.Errorf("unable to decode keystore\n %w", err)
}

return ks, nil
}

func (c CertificateLoader) writeKeyStore(ks keystore.KeyStore, path string, password string) error {
if unix.Access(path, unix.W_OK) != nil {
_, _ = fmt.Fprintf(c.Logger, "WARNING: Unable to add container CA certificates to JVM because %s is read-only", path)
return nil
}

out, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("unable to open %s\n%w", path, err)
}
defer out.Close()

if err := ks.Store(out, []byte(password)); err != nil {
return fmt.Errorf("unable to encode keystore\n%w", err)
}

return nil
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/sclevine/spec v1.4.0
github.com/stretchr/testify v1.8.4
golang.org/x/sys v0.14.0
software.sslmate.com/src/go-pkcs12 v0.3.0
)

require (
Expand All @@ -30,6 +31,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.1 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
Expand All @@ -79,3 +81,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
software.sslmate.com/src/go-pkcs12 v0.3.0 h1:ZYaL72OA2n9UgvesM62z1xmb4PYjgzswQ7xkuC08FEI=
software.sslmate.com/src/go-pkcs12 v0.3.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
1 change: 1 addition & 0 deletions init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,6 @@ func TestUnit(t *testing.T) {
suite("SDKMAN", testSDKMAN)
suite("Versions", testVersions)
suite("JVMVersions", testJVMVersion)
suite("Keystore", testKeystore)
suite.Run(t)
}
161 changes: 161 additions & 0 deletions keystore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* Copyright 2018-2020 the original author or authors.
*
* 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
*
* https://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 libjvm

import (
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"os"

"github.com/pavlo-v-chernykh/keystore-go/v4"
"golang.org/x/sys/unix"
"software.sslmate.com/src/go-pkcs12"
)

type Keystore interface {
Add(string, *pem.Block) error
Write() error
}

func DetectKeystore(location string) (Keystore, error) {
buf, err := os.ReadFile(location)
if err != nil {
return nil, err
}

if len(buf) == 0 {
return nil, errors.New("empty keystore found")
}

if len(buf) > 3 && buf[0] == 0xFE && buf[1] == 0xED && buf[2] == 0xFE && buf[3] == 0xED {
return NewJKSKeystore(location, "changeit")
}

return NewPKCS12Keystore(location), nil
}

var _ Keystore = &JKSKeystore{}

type JKSKeystore struct {
location string
password string
store keystore.KeyStore
}

func NewJKSKeystore(location, password string) (*JKSKeystore, error) {
in, err := os.Open(location)
if err != nil {
return nil, fmt.Errorf("unable to open %s\n%w", location, err)
}
defer in.Close()

ks := keystore.New(keystore.WithOrderedAliases())
if err := ks.Load(in, []byte(password)); err != nil {
return nil, fmt.Errorf("unable to decode keystore\n %w", err)
}
return &JKSKeystore{
location: location,
password: password,
store: ks,
}, nil
}

func (k *JKSKeystore) Add(name string, b *pem.Block) error {
entry := keystore.TrustedCertificateEntry{
CreationTime: NormalizedDateTime,
Certificate: keystore.Certificate{
Type: "X.509",
Content: b.Bytes,
},
}
if err := k.store.SetTrustedCertificateEntry(name, entry); err != nil {
return fmt.Errorf("unable to add trusted entry\n%w", err)
}
return nil
}

func (k *JKSKeystore) Write() error {
if unix.Access(k.location, unix.W_OK) != nil {
return nil
}

out, err := os.OpenFile(k.location, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("unable to open %s\n%w", k.location, err)
}
defer out.Close()

if err := k.store.Store(out, []byte(k.password)); err != nil {
return fmt.Errorf("unable to encode keystore\n%w", err)
}

return nil
}

func (k *JKSKeystore) Len() int {
return len(k.store.Aliases())
}

var _ Keystore = &PKCS12Keystore{}

type PKCS12Keystore struct {
location string
entries []pkcs12.TrustStoreEntry
}

func NewPKCS12Keystore(location string) *PKCS12Keystore {
return &PKCS12Keystore{
location: location,
entries: []pkcs12.TrustStoreEntry{},
}
}

func (k *PKCS12Keystore) Add(name string, b *pem.Block) error {
cert, err := x509.ParseCertificate(b.Bytes)
if err != nil {
return err
}

k.entries = append(k.entries, pkcs12.TrustStoreEntry{
Cert: cert,
FriendlyName: name,
})

return nil
}

func (k *PKCS12Keystore) Write() error {
out, err := os.OpenFile(k.location, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer out.Close()
data, err := pkcs12.Passwordless.EncodeTrustStoreEntries(k.entries, "")
if err != nil {
return err
}
_, err = out.Write(data)
return err
}

func (k *PKCS12Keystore) Len() int {
data, _ := os.ReadFile(k.location)
certs, _ := pkcs12.DecodeTrustStore(data, "")
return len(certs) + len(k.entries)
}
Loading

0 comments on commit 6a9b8a0

Please sign in to comment.