Skip to content

Commit

Permalink
Merge pull request #20 from pavel-v-chernykh/reproduce-java-keystore-api
Browse files Browse the repository at this point in the history
Reproduce Java KeyStore API
  • Loading branch information
pavlo-v-chernykh authored Oct 31, 2020
2 parents f2a310a + adfaf7d commit 3489d09
Show file tree
Hide file tree
Showing 20 changed files with 860 additions and 417 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: '1.14.4'
go-version: '1.15.3'
- name: Fmt
run: go fmt github.com/pavel-v-chernykh/keystore-go/v3/...
lint:
Expand All @@ -23,7 +23,7 @@ jobs:
uses: golangci/[email protected]
with:
args: --timeout=5m0s -c .golangci.yaml
version: v1.30
version: v1.32
test:
name: Test
runs-on: ubuntu-latest
Expand All @@ -33,6 +33,6 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: '1.14.4'
go-version: '1.15.3'
- name: Test
run: go test -cover -count=1 -v github.com/pavel-v-chernykh/keystore-go/v3/...
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ _testmain.go
*.prof
*.iml
.idea
coverage.out
1 change: 1 addition & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ linters:
- funlen
- goerr113
- gofumpt
- exhaustivestruct

linters-settings:
gomnd:
Expand Down
18 changes: 18 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,27 @@ fmt:
lint:
golangci-lint run -c .golangci.yaml

lint-examples:
cd examples/compare && golangci-lint run -c ../../.golangci.yaml
cd examples/keypass && golangci-lint run -c ../../.golangci.yaml
cd examples/pem && golangci-lint run -c ../../.golangci.yaml
cd examples/truststore && golangci-lint run -c ../../.golangci.yaml

run-examples:
cd examples/compare && go run main.go
cd examples/keypass && go run main.go
cd examples/pem && go run main.go
cd examples/truststore && go run main.go "/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home/jre/lib/security/cacerts" "changeit"

test:
go test -cover -count=1 -v ./...

test-coverprofile:
go test -coverprofile=coverage.out -cover -count=1 -v ./...

cover:
go tool cover -html=coverage.out

all: fmt lint test

.PHONY: fmt lint test all
Expand Down
35 changes: 24 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,45 @@ import (
"log"
"os"
"reflect"
"github.com/pavel-v-chernykh/keystore-go/v3"

"github.com/pavel-v-chernykh/keystore-go/v4"
)

func readKeyStore(filename string, password []byte) keystore.KeyStore {
f, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
defer f.Close()
keyStore, err := keystore.Decode(f, password)
if err != nil {
log.Fatal(err)

defer func() {
if err := f.Close(); err != nil {
log.Fatal(err)
}
}()

keyStore := keystore.New()
if err := keyStore.Load(f, password); err != nil {
log.Fatal(err) // nolint: gocritic
}

return keyStore
}

func writeKeyStore(keyStore keystore.KeyStore, filename string, password []byte) {
o, err := os.Create(filename)
f, err := os.Create(filename)
if err != nil {
log.Fatal(err)
}
defer o.Close()
err = keystore.Encode(o, keyStore, password)

defer func() {
if err := f.Close(); err != nil {
log.Fatal(err)
}
}()

err = keyStore.Store(f, password)
if err != nil {
log.Fatal(err)
log.Fatal(err) // nolint: gocritic
}
}

Expand All @@ -56,7 +69,7 @@ func main() {

ks2 := readKeyStore("keystore2.jks", password)

log.Printf("Is equal: %v\n", reflect.DeepEqual(ks1, ks2))
log.Printf("is equal: %v\n", reflect.DeepEqual(ks1, ks2))
}
```

Expand Down
135 changes: 25 additions & 110 deletions decoder.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package keystore

import (
"bytes"
"crypto/sha1"
"errors"
"fmt"
"hash"
Expand Down Expand Up @@ -97,7 +95,7 @@ func (ksd *keyStoreDecoder) readString() (string, error) {
return string(strBody), nil
}

func (ksd *keyStoreDecoder) readCertificate(version uint32) (*Certificate, error) {
func (ksd *keyStoreDecoder) readCertificate(version uint32) (Certificate, error) {
var certType string

switch version {
Expand All @@ -106,106 +104,95 @@ func (ksd *keyStoreDecoder) readCertificate(version uint32) (*Certificate, error
case version02:
readCertType, err := ksd.readString()
if err != nil {
return nil, fmt.Errorf("read type: %w", err)
return Certificate{}, fmt.Errorf("read type: %w", err)
}

certType = readCertType
default:
return nil, errors.New("got unknown version")
return Certificate{}, errors.New("got unknown version")
}

certLen, err := ksd.readUint32()
if err != nil {
return nil, fmt.Errorf("read length: %w", err)
return Certificate{}, fmt.Errorf("read length: %w", err)
}

certContent, err := ksd.readBytes(certLen)
if err != nil {
return nil, fmt.Errorf("read content: %w", err)
return Certificate{}, fmt.Errorf("read content: %w", err)
}

certificate := Certificate{
Type: certType,
Content: certContent,
}

return &certificate, nil
return certificate, nil
}

func (ksd *keyStoreDecoder) readPrivateKeyEntry(version uint32, password []byte) (*PrivateKeyEntry, error) {
func (ksd *keyStoreDecoder) readPrivateKeyEntry(version uint32) (PrivateKeyEntry, error) {
creationTimeStamp, err := ksd.readUint64()
if err != nil {
return nil, fmt.Errorf("read creation timestamp: %w", err)
return PrivateKeyEntry{}, fmt.Errorf("read creation timestamp: %w", err)
}

length, err := ksd.readUint32()
if err != nil {
return nil, fmt.Errorf("read length: %w", err)
return PrivateKeyEntry{}, fmt.Errorf("read length: %w", err)
}

encryptedPrivateKey, err := ksd.readBytes(length)
if err != nil {
return nil, fmt.Errorf("read encrypted private key: %w", err)
return PrivateKeyEntry{}, fmt.Errorf("read encrypted private key: %w", err)
}

certNum, err := ksd.readUint32()
if err != nil {
return nil, fmt.Errorf("read number of certificates: %w", err)
return PrivateKeyEntry{}, fmt.Errorf("read number of certificates: %w", err)
}

chain := make([]Certificate, 0, certNum)

for i := uint32(0); i < certNum; i++ {
cert, err := ksd.readCertificate(version)
if err != nil {
return nil, fmt.Errorf("read %d certificate: %w", i, err)
return PrivateKeyEntry{}, fmt.Errorf("read %d certificate: %w", i, err)
}

chain = append(chain, *cert)
}

decryptedPrivateKey, err := decrypt(encryptedPrivateKey, password)
if err != nil {
return nil, fmt.Errorf("decrypt content: %w", err)
chain = append(chain, cert)
}

creationDateTime := millisecondsToTime(int64(creationTimeStamp))
privateKeyEntry := PrivateKeyEntry{
Entry: Entry{
CreationTime: creationDateTime,
},
PrivateKey: decryptedPrivateKey,
CertificateChain: chain,
encryptedPrivateKey: encryptedPrivateKey,
CreationTime: creationDateTime,
CertificateChain: chain,
}

return &privateKeyEntry, nil
return privateKeyEntry, nil
}

func (ksd *keyStoreDecoder) readTrustedCertificateEntry(version uint32) (*TrustedCertificateEntry, error) {
func (ksd *keyStoreDecoder) readTrustedCertificateEntry(version uint32) (TrustedCertificateEntry, error) {
creationTimeStamp, err := ksd.readUint64()
if err != nil {
return nil, fmt.Errorf("read creation timestamp: %w", err)
return TrustedCertificateEntry{}, fmt.Errorf("read creation timestamp: %w", err)
}

certificate, err := ksd.readCertificate(version)
if err != nil {
return nil, fmt.Errorf("read certificate: %w", err)
return TrustedCertificateEntry{}, fmt.Errorf("read certificate: %w", err)
}

creationDateTime := millisecondsToTime(int64(creationTimeStamp))
trustedCertificateEntry := TrustedCertificateEntry{
Entry: Entry{
CreationTime: creationDateTime,
},
Certificate: *certificate,
CreationTime: creationDateTime,
Certificate: certificate,
}

return &trustedCertificateEntry, nil
return trustedCertificateEntry, nil
}

func (ksd *keyStoreDecoder) readEntry(
version uint32, storePassword []byte, keysPasswords ...KeyPassword,
) (string, interface{}, error) {
func (ksd *keyStoreDecoder) readEntry(version uint32) (string, interface{}, error) {
tag, err := ksd.readUint32()
if err != nil {
return "", nil, fmt.Errorf("read tag: %w", err)
Expand All @@ -218,15 +205,7 @@ func (ksd *keyStoreDecoder) readEntry(

switch tag {
case privateKeyTag:
keyPassword := storePassword

for _, kp := range keysPasswords {
if kp.Alias == alias {
keyPassword = kp.Password
}
}

entry, err := ksd.readPrivateKeyEntry(version, keyPassword)
entry, err := ksd.readPrivateKeyEntry(version)
if err != nil {
return "", nil, fmt.Errorf("read private key entry: %w", err)
}
Expand All @@ -243,67 +222,3 @@ func (ksd *keyStoreDecoder) readEntry(
return "", nil, errors.New("got unknown entry tag")
}
}

// Decode reads keystore representation from r then decrypts and check signature using password.
// It is strongly recommended to fill password slice with zero after usage.
// keysPasswords can be used to decrypt private key entries with passwords other then storePassword.
func Decode(r io.Reader, storePassword []byte, keysPasswords ...KeyPassword) (KeyStore, error) {
ksd := keyStoreDecoder{
r: r,
md: sha1.New(),
}

storePasswordBytes := passwordBytes(storePassword)
defer zeroing(storePasswordBytes)

if _, err := ksd.md.Write(storePasswordBytes); err != nil {
return nil, fmt.Errorf("update digest with password: %w", err)
}

if _, err := ksd.md.Write(whitenerMessage); err != nil {
return nil, fmt.Errorf("update digest with whitener message: %w", err)
}

readMagic, err := ksd.readUint32()
if err != nil {
return nil, fmt.Errorf("read magic: %w", err)
}

if readMagic != magic {
return nil, errors.New("got invalid magic")
}

version, err := ksd.readUint32()
if err != nil {
return nil, fmt.Errorf("read version: %w", err)
}

entryNum, err := ksd.readUint32()
if err != nil {
return nil, fmt.Errorf("read number of entries: %w", err)
}

keyStore := make(KeyStore, entryNum)

for i := uint32(0); i < entryNum; i++ {
alias, entry, err := ksd.readEntry(version, storePassword, keysPasswords...)
if err != nil {
return nil, fmt.Errorf("read %d entry: %w", i, err)
}

keyStore[alias] = entry
}

computedDigest := ksd.md.Sum(nil)

actualDigest, err := ksd.readBytes(uint32(ksd.md.Size()))
if err != nil {
return nil, fmt.Errorf("read digest: %w", err)
}

if !bytes.Equal(actualDigest, computedDigest) {
return nil, errors.New("got invalid digest")
}

return keyStore, nil
}
Loading

0 comments on commit 3489d09

Please sign in to comment.