forked from pavlo-v-chernykh/keystore-go
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
247 additions
and
141 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package keystore | ||
|
||
import ( | ||
"bytes" | ||
"crypto/sha1" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"software.sslmate.com/src/go-pkcs12" | ||
) | ||
|
||
// Load reads keystore representation from r and checks its signature. | ||
// It is strongly recommended to fill password slice with zero after usage. | ||
func (ks KeyStore) Load(r io.Reader, password []byte) error { | ||
d := decoder{ | ||
r: r, | ||
h: sha1.New(), | ||
} | ||
|
||
fourBytes, err := d.readBytes(4) //nolint:gomnd,mnd | ||
if err != nil { | ||
return fmt.Errorf("read magic: %w", err) | ||
} | ||
|
||
magicBytesReader := bytes.NewReader(fourBytes) | ||
fullReader := io.MultiReader(magicBytesReader, r) | ||
|
||
if bytes.Equal(fourBytes, jksMagicBytes) { | ||
return ks.loadJks(fullReader, password) | ||
} | ||
|
||
return ks.loadPkcs12(fullReader, password) | ||
} | ||
|
||
// loads the old JKS format. | ||
func (ks KeyStore) loadJks(r io.Reader, password []byte) error { | ||
d := decoder{ | ||
r: r, | ||
h: sha1.New(), | ||
} | ||
|
||
passwordBytes := passwordBytes(password) | ||
defer zeroing(passwordBytes) | ||
|
||
if _, err := d.h.Write(passwordBytes); err != nil { | ||
return fmt.Errorf("update digest with password: %w", err) | ||
} | ||
|
||
if _, err := d.h.Write(whitenerMessage); err != nil { | ||
return fmt.Errorf("update digest with whitener message: %w", err) | ||
} | ||
|
||
fourBytes, err := d.readBytes(4) //nolint:gomnd,mnd | ||
if err != nil { | ||
return fmt.Errorf("read magic: %w", err) | ||
} | ||
|
||
if !bytes.Equal(fourBytes, jksMagicBytes) { | ||
return errors.New("got invalid magic bytes from the file, this is no JKS format") | ||
} | ||
|
||
version, err := d.readUint32() | ||
if err != nil { | ||
return fmt.Errorf("read version: %w", err) | ||
} | ||
|
||
entryNum, err := d.readUint32() | ||
if err != nil { | ||
return fmt.Errorf("read number of entries: %w", err) | ||
} | ||
|
||
for i := range entryNum { | ||
alias, entry, err := d.readEntry(version) | ||
if err != nil { | ||
return fmt.Errorf("read %d entry: %w", i, err) | ||
} | ||
|
||
ks.m[alias] = entry | ||
} | ||
|
||
computedDigest := d.h.Sum(nil) | ||
|
||
actualDigest, err := d.readBytes(uint32(d.h.Size())) //nolint:gosec | ||
if err != nil { | ||
return fmt.Errorf("read digest: %w", err) | ||
} | ||
|
||
if !bytes.Equal(actualDigest, computedDigest) { | ||
return errors.New("got invalid digest") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// loads the newer PKCS12 format. | ||
func (ks KeyStore) loadPkcs12(r io.Reader, password []byte) error { | ||
allData, err := io.ReadAll(r) | ||
if err != nil { | ||
return fmt.Errorf("can't read pkcs12 data: %w", err) | ||
} | ||
|
||
certs, err := pkcs12.DecodeTrustStore(allData, string(password)) | ||
if err != nil { | ||
return fmt.Errorf("can't decode pkcs12 trust strore: %w", err) | ||
} | ||
|
||
for _, cert := range certs { | ||
certificate := Certificate{ | ||
Type: "X509", | ||
Content: nil, | ||
} | ||
|
||
tce := TrustedCertificateEntry{} | ||
tce.CreationTime = cert.NotBefore // there is no better timestamp provided by pkcs12 file | ||
tce.Certificate = certificate | ||
alias := fmt.Sprintf("c_%s,o_%s,ou_%s,cn_%s,s_%s", | ||
cert.Subject.Country, | ||
cert.Subject.Organization, | ||
cert.Subject.OrganizationalUnit, | ||
cert.Subject.CommonName, | ||
cert.Subject.SerialNumber, | ||
) | ||
//Country, Organization, OrganizationalUnit []string | ||
//Locality, Province []string | ||
//StreetAddress, PostalCode []string | ||
//SerialNumber, CommonName string | ||
ks.m[alias] = tce | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package keystore | ||
|
||
import ( | ||
"encoding/pem" | ||
"os" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestLoad(t *testing.T) { | ||
password := []byte{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'} | ||
defer zeroing(password) | ||
|
||
f, err := os.Open("./testdata/keystore.jks") | ||
require.NoError(t, err) | ||
|
||
defer func() { | ||
err := f.Close() | ||
require.NoError(t, err) | ||
}() | ||
|
||
keyStore := New() | ||
|
||
err = keyStore.Load(f, password) | ||
require.NoError(t, err) | ||
|
||
actualPKE, err := keyStore.GetPrivateKeyEntry("alias", password) | ||
require.NoError(t, err) | ||
|
||
expectedCT, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", "2017-09-19 17:41:00.016 +0300 EEST") | ||
require.NoError(t, err) | ||
|
||
assert.Truef(t, actualPKE.CreationTime.Equal(expectedCT), | ||
"unexpected private key entry creation time: '%v' '%v'", actualPKE.CreationTime, expectedCT) | ||
|
||
assert.Empty(t, actualPKE.CertificateChain, "unexpected private key entry certificate chain length") | ||
|
||
pkPEM, err := os.ReadFile("./testdata/key.pem") | ||
require.NoError(t, err) | ||
|
||
decodedPK, _ := pem.Decode(pkPEM) | ||
|
||
assert.Equal(t, decodedPK.Bytes, actualPKE.PrivateKey, "unexpected private key") | ||
} | ||
|
||
func TestLoadKeyPassword(t *testing.T) { | ||
password := []byte{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'} | ||
defer zeroing(password) | ||
|
||
keyPassword := []byte{'k', 'e', 'y', 'p', 'a', 's', 's', 'w', 'o', 'r', 'd'} | ||
defer zeroing(keyPassword) | ||
|
||
f, err := os.Open("./testdata/keystore_keypass.jks") | ||
require.NoError(t, err) | ||
|
||
defer func() { | ||
err := f.Close() | ||
require.NoError(t, err) | ||
}() | ||
|
||
keyStore := New() | ||
|
||
err = keyStore.Load(f, password) | ||
require.NoError(t, err) | ||
|
||
actualPKE, err := keyStore.GetPrivateKeyEntry("alias", keyPassword) | ||
require.NoError(t, err) | ||
|
||
expectedCT, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", "2020-10-26 12:01:38.387 +0200 EET") | ||
require.NoError(t, err) | ||
|
||
assert.Truef(t, actualPKE.CreationTime.Equal(expectedCT), | ||
"unexpected private key entry creation time: '%v' '%v'", actualPKE.CreationTime, expectedCT) | ||
|
||
assert.Lenf(t, actualPKE.CertificateChain, 1, | ||
"unexpected private key entry certificate chain length: '%d' '%d'", len(actualPKE.CertificateChain), 0) | ||
|
||
pkPEM, err := os.ReadFile("./testdata/key_keypass.pem") | ||
require.NoError(t, err) | ||
|
||
decodedPK, _ := pem.Decode(pkPEM) | ||
|
||
assert.Equal(t, decodedPK.Bytes, actualPKE.PrivateKey, "unexpected private key") | ||
} | ||
|
||
func TestLoadPkcs12(t *testing.T) { | ||
password := []byte("") | ||
|
||
f, err := os.Open("./testdata/keystore_temurin_openjdk_21.0.4_lts.p12") | ||
require.NoError(t, err) | ||
|
||
defer func() { | ||
err := f.Close() | ||
require.NoError(t, err) | ||
}() | ||
|
||
keyStore := New() | ||
|
||
err = keyStore.Load(f, password) | ||
require.NoError(t, err) | ||
|
||
assert.Len(t, keyStore.Aliases(), 148) | ||
} |
Oops, something went wrong.