mirror of
https://github.com/square/certigo.git
synced 2024-04-21 12:32:40 +00:00
Add jceks sub-package
This commit is contained in:
@@ -14,3 +14,6 @@ install:
|
||||
|
||||
before_script:
|
||||
- make check
|
||||
|
||||
script:
|
||||
- make test
|
||||
|
||||
@@ -7,6 +7,9 @@ depends:
|
||||
build:
|
||||
go build .
|
||||
|
||||
test:
|
||||
go test -v `glide novendor`
|
||||
|
||||
check:
|
||||
go vet -v .
|
||||
go vet -v `glide novendor`
|
||||
!(gofmt -d $(SOURCE_FILES) | grep .)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# JCEKS
|
||||
|
||||
Package jceks parses JCEKS (Java Cryptogaphy Extension Key Store)
|
||||
files and extracts keys and certificates. This module only implements
|
||||
a fraction of the JCEKS cryptographic protocols. In particular, it
|
||||
implements the SHA1 signature verification of the key store and the
|
||||
PBEWithMD5AndDES3CBC cipher for encrypting private keys.
|
||||
+426
@@ -0,0 +1,426 @@
|
||||
/*-
|
||||
* Copyright 2016 Square Inc.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 jceks parses JCEKS (Java Cryptogaphy Extension Key Store)
|
||||
// files and extracts keys and certificates. This module only implements
|
||||
// a fraction of the JCEKS cryptographic protocols. In particular, it
|
||||
// implements the SHA1 signature verification of the key store and the
|
||||
// PBEWithMD5AndDES3CBC cipher for encrypting private keys.
|
||||
package jceks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/subtle"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
jceksMagic = 0xcececece
|
||||
jceksVersion = 0x02
|
||||
jksMagic = 0xfeedfeed
|
||||
)
|
||||
|
||||
var (
|
||||
oidKeyProtector = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 42, 2, 17, 1, 1}
|
||||
oidPublicKeyRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
|
||||
)
|
||||
|
||||
type encryptedPrivateKeyInfo struct {
|
||||
Algo pkix.AlgorithmIdentifier
|
||||
EncryptedKey []byte
|
||||
}
|
||||
|
||||
type privateKeyInfo struct {
|
||||
Version int
|
||||
Algo pkix.AlgorithmIdentifier
|
||||
PrivateKey []byte
|
||||
}
|
||||
|
||||
type privateKeyEntry struct {
|
||||
date time.Time
|
||||
encodedKey []byte
|
||||
certs []*x509.Certificate
|
||||
}
|
||||
|
||||
func (e *privateKeyEntry) String() string {
|
||||
return fmt.Sprintf("private-key: %s", e.date)
|
||||
}
|
||||
|
||||
func (e *privateKeyEntry) Recover(password []byte) (*rsa.PrivateKey, error) {
|
||||
var eKey encryptedPrivateKeyInfo
|
||||
_, err := asn1.Unmarshal(e.encodedKey, &eKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var decryptedKey []byte
|
||||
switch {
|
||||
case eKey.Algo.Algorithm.Equal(oidPBEWithMD5AndDES3CBC):
|
||||
decryptedKey, err = recoverPBEWithMD5AndDES3CBC(eKey.Algo, eKey.EncryptedKey, password)
|
||||
case eKey.Algo.Algorithm.Equal(oidKeyProtector):
|
||||
// JavaSoft proprietary key-protection algorithm (used to protect
|
||||
// private keys in the keystore implementation that comes with JDK
|
||||
// 1.2). We shouldn't need this.
|
||||
fallthrough
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported encrypted-private-key algorithm: %v", eKey.Algo.Algorithm)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pKey privateKeyInfo
|
||||
if _, err := asn1.Unmarshal(decryptedKey, &pKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !pKey.Algo.Algorithm.Equal(oidPublicKeyRSA) {
|
||||
return nil, fmt.Errorf("unsupported private-key algorithm: %v", pKey.Algo.Algorithm)
|
||||
}
|
||||
return x509.ParsePKCS1PrivateKey(pKey.PrivateKey)
|
||||
}
|
||||
|
||||
type trustedCertEntry struct {
|
||||
date time.Time
|
||||
cert *x509.Certificate
|
||||
}
|
||||
|
||||
func (e *trustedCertEntry) String() string {
|
||||
return fmt.Sprintf("trusted-cert: %s", e.date)
|
||||
}
|
||||
|
||||
// KeyStore maintains a map from alias name to the entry for that
|
||||
// alias. Entries are currently either privateKeyEntry or
|
||||
// trustedCertEntry.
|
||||
type KeyStore struct {
|
||||
entries map[string]interface{}
|
||||
}
|
||||
|
||||
// readUTF reads a java encoded UTF-8 string. The encoding provides a
|
||||
// 2-byte prefix indicating the length of the string.
|
||||
func readUTF(r io.Reader) (string, error) {
|
||||
var length uint16
|
||||
err := binary.Read(r, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf := make([]byte, length)
|
||||
_, err = io.ReadFull(r, buf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
// readBytes reads a byte array from the reader. The encoding provides
|
||||
// a 4-byte prefix indicating the number of bytes which follow.
|
||||
func readBytes(r io.Reader) ([]byte, error) {
|
||||
length, err := readInt32(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf := make([]byte, length)
|
||||
_, err = io.ReadFull(r, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func readInt32(r io.Reader) (int32, error) {
|
||||
var v int32
|
||||
err := binary.Read(r, binary.BigEndian, &v)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func readUint32(r io.Reader) (uint32, error) {
|
||||
var v uint32
|
||||
err := binary.Read(r, binary.BigEndian, &v)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func readDate(r io.Reader) (time.Time, error) {
|
||||
var v int64
|
||||
err := binary.Read(r, binary.BigEndian, &v)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
sec := v / 1000
|
||||
nsec := (v - sec*1000) * 1000 * 1000
|
||||
return time.Unix(sec, nsec), nil
|
||||
}
|
||||
|
||||
// Returns a SHA1 hash which has been pre-keyed with the specified
|
||||
// password according to the JCEKS algorithm.
|
||||
func getPreKeyedHash(password []byte) hash.Hash {
|
||||
md := sha1.New()
|
||||
buf := make([]byte, len(password)*2)
|
||||
for i := 0; i < len(password); i++ {
|
||||
buf[i*2+1] = password[i]
|
||||
}
|
||||
md.Write(buf)
|
||||
// Yes, "Mighty Aprhodite" is a constant used by this method.
|
||||
md.Write([]byte("Mighty Aphrodite"))
|
||||
return md
|
||||
}
|
||||
|
||||
func parseHeader(r io.Reader) (uint32, error) {
|
||||
magic, err := readUint32(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if magic != jceksMagic && magic != jksMagic {
|
||||
return 0, fmt.Errorf("unexpected magic: %08x != (%08x || %08x)", magic, uint32(jceksMagic), uint32(jksMagic))
|
||||
}
|
||||
version, err := readUint32(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func (ks *KeyStore) parsePrivateKey(r io.Reader) error {
|
||||
alias, err := readUTF(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entry := &privateKeyEntry{
|
||||
certs: []*x509.Certificate{},
|
||||
}
|
||||
entry.date, err = readDate(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entry.encodedKey, err = readBytes(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nCerts, err := readInt32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for j := 0; j < int(nCerts); j++ {
|
||||
certType, err := readUTF(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if certType != "X.509" {
|
||||
return fmt.Errorf("unable to handle certificate type: %s", certType)
|
||||
}
|
||||
certBytes, err := readBytes(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert, err := x509.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
entry.certs = append(entry.certs, cert)
|
||||
}
|
||||
ks.entries[alias] = entry
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ks *KeyStore) parseTrustedCert(r io.Reader) error {
|
||||
alias, err := readUTF(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entry := &trustedCertEntry{}
|
||||
entry.date, err = readDate(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certType, err := readUTF(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if certType != "X.509" {
|
||||
return fmt.Errorf("unable to handle certificate type: %s", certType)
|
||||
}
|
||||
certBytes, err := readBytes(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entry.cert, err = x509.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ks.entries[alias] = entry
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse parses the key store from the specified reader.
|
||||
func (ks *KeyStore) Parse(r io.Reader, password []byte) error {
|
||||
var md hash.Hash
|
||||
if password != nil {
|
||||
md = getPreKeyedHash(password)
|
||||
r = io.TeeReader(r, md)
|
||||
}
|
||||
|
||||
version, err := parseHeader(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if version != jceksVersion {
|
||||
return fmt.Errorf("unexpected version: %d != %d", version, jceksVersion)
|
||||
}
|
||||
|
||||
count, err := readInt32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < int(count); i++ {
|
||||
tag, err := readInt32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch tag {
|
||||
case 1:
|
||||
// Private-key entry
|
||||
err := ks.parsePrivateKey(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case 2:
|
||||
// Trusted-cert entry
|
||||
err := ks.parseTrustedCert(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case 3:
|
||||
// Secret-key entry
|
||||
fallthrough
|
||||
default:
|
||||
panic(fmt.Errorf("unimplemented tag: %d", tag))
|
||||
}
|
||||
}
|
||||
|
||||
if md != nil {
|
||||
computed := md.Sum([]byte{})
|
||||
actual := make([]byte, len(computed))
|
||||
_, err := io.ReadFull(r, actual)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if subtle.ConstantTimeCompare(computed, actual) != 1 {
|
||||
return fmt.Errorf("keystore was tampered with or password was incorrect")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPrivateKeyAndCerts retrieves the specified private key. Returns
|
||||
// nil if the private key does not exist or alias points to a non
|
||||
// private key entry.
|
||||
func (ks *KeyStore) GetPrivateKeyAndCerts(alias string, password []byte) (
|
||||
key *rsa.PrivateKey, certs []*x509.Certificate, err error) {
|
||||
|
||||
entry := ks.entries[alias]
|
||||
if entry == nil {
|
||||
return
|
||||
}
|
||||
switch t := entry.(type) {
|
||||
case *privateKeyEntry:
|
||||
if len(t.certs) < 1 {
|
||||
return nil, nil, fmt.Errorf("key has no certificates")
|
||||
}
|
||||
if (t.certs[0].KeyUsage & x509.KeyUsageDigitalSignature) == 0 {
|
||||
return nil, nil, fmt.Errorf("key cannot be used for digital signatures: %x", t.certs[0].KeyUsage)
|
||||
}
|
||||
key, err = t.Recover(password)
|
||||
if err == nil {
|
||||
certs = t.certs
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetCert retrieves the specified certificate. Returns nil if the
|
||||
// certificate does not exist or alias points to a non certificate
|
||||
// entry.
|
||||
func (ks *KeyStore) GetCert(alias string) (*x509.Certificate, error) {
|
||||
entry := ks.entries[alias]
|
||||
if entry == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch t := entry.(type) {
|
||||
case *trustedCertEntry:
|
||||
return t.cert, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ListPrivateKeys lists the names of the private keys stored in the key store.
|
||||
func (ks *KeyStore) ListPrivateKeys() []string {
|
||||
var r []string
|
||||
for k, v := range ks.entries {
|
||||
if _, ok := v.(*privateKeyEntry); ok {
|
||||
r = append(r, k)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// ListCerts lists the names of the certs stored in the key store.
|
||||
func (ks *KeyStore) ListCerts() []string {
|
||||
var r []string
|
||||
for k, v := range ks.entries {
|
||||
if _, ok := v.(*trustedCertEntry); ok {
|
||||
r = append(r, k)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (ks *KeyStore) String() string {
|
||||
var buf bytes.Buffer
|
||||
for k, v := range ks.entries {
|
||||
fmt.Fprintf(&buf, "%s\n", k)
|
||||
fmt.Fprintf(&buf, " %s\n", v)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Load loads the key store from the specified file.
|
||||
func Load(filename string, password []byte) (*KeyStore, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
ks := &KeyStore{
|
||||
entries: make(map[string]interface{}),
|
||||
}
|
||||
err = ks.Parse(file, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ks, err
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/*-
|
||||
* Copyright 2016 Square Inc.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 jceks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
generateTestData = flag.Bool("generate", false, "generate test data")
|
||||
)
|
||||
|
||||
type testData struct {
|
||||
certFilename string
|
||||
keyFilename string
|
||||
p12Filename string
|
||||
jceksFilename string
|
||||
storePassword string
|
||||
keyPassword string
|
||||
alias string
|
||||
}
|
||||
|
||||
func newTestData(prefix string) *testData {
|
||||
return &testData{
|
||||
certFilename: "testdata/" + prefix + ".crt",
|
||||
keyFilename: "testdata/" + prefix + ".key",
|
||||
p12Filename: "testdata/" + prefix + ".p12",
|
||||
jceksFilename: "testdata/" + prefix + ".jceks",
|
||||
storePassword: prefix + "-store-password",
|
||||
keyPassword: prefix + "-key-password",
|
||||
alias: prefix + "-some-alias",
|
||||
}
|
||||
}
|
||||
|
||||
func (d *testData) cleanup() {
|
||||
os.Remove(d.certFilename)
|
||||
os.Remove(d.keyFilename)
|
||||
os.Remove(d.p12Filename)
|
||||
os.Remove(d.jceksFilename)
|
||||
}
|
||||
|
||||
func runCommand(name string, args ...string) (string, error) {
|
||||
cmd := exec.Command(name, args...)
|
||||
var buf bytes.Buffer
|
||||
cmd.Stdout = &buf
|
||||
cmd.Stderr = &buf
|
||||
err := cmd.Run()
|
||||
out := buf.Bytes()
|
||||
if err != nil {
|
||||
return "", errors.New(string(out))
|
||||
}
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
func (d *testData) generatePrivateKeyAndCert(t *testing.T) {
|
||||
_, err := runCommand("openssl", "req", "-x509",
|
||||
"-nodes", "-days", "365", "-newkey", "rsa:2048",
|
||||
"-subj", "/CN=Test User/O=Test Organization/C=US",
|
||||
"-extensions", "v3_req",
|
||||
"-keyout", d.keyFilename,
|
||||
"-out", d.certFilename)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *testData) convertPrivateKeyAndCertToPkcs12(t *testing.T) {
|
||||
_, err := runCommand("openssl", "pkcs12", "-export",
|
||||
"-in", d.certFilename,
|
||||
"-inkey", d.keyFilename,
|
||||
"-name", d.alias,
|
||||
"-out", d.p12Filename,
|
||||
"-passout", fmt.Sprintf("pass:%s", d.storePassword))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *testData) convertPkcs12ToJceks(t *testing.T) {
|
||||
_, err := runCommand("keytool", "-importkeystore",
|
||||
"-alias", d.alias,
|
||||
"-destkeypass", d.keyPassword,
|
||||
"-destkeystore", d.jceksFilename,
|
||||
"-deststorepass", d.storePassword,
|
||||
"-srckeystore", d.p12Filename,
|
||||
"-srcstoretype", "PKCS12",
|
||||
"-srcstorepass", d.storePassword,
|
||||
"-storetype", "JCEKS")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *testData) importCertToJceks(t *testing.T) {
|
||||
_, err := runCommand("keytool", "-importcert", "-noprompt",
|
||||
"-alias", d.alias,
|
||||
"-file", d.certFilename,
|
||||
"-keystore", d.jceksFilename,
|
||||
"-storepass", d.storePassword,
|
||||
"-storetype", "JCEKS")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func equalRSAPublicKey(a, b *rsa.PublicKey) bool {
|
||||
if a.E != b.E {
|
||||
return false
|
||||
}
|
||||
return a.N.Cmp(b.N) == 0
|
||||
}
|
||||
|
||||
func equalRSAPrivateKey(a, b *rsa.PrivateKey) bool {
|
||||
if !equalRSAPublicKey(&a.PublicKey, &b.PublicKey) {
|
||||
return false
|
||||
}
|
||||
if a.D.Cmp(b.D) != 0 {
|
||||
return false
|
||||
}
|
||||
if len(a.Primes) != len(b.Primes) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(a.Primes); i++ {
|
||||
if a.Primes[i].Cmp(b.Primes[i]) != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestPrivateKey(t *testing.T) {
|
||||
d := newTestData("private-key")
|
||||
if *generateTestData {
|
||||
d.cleanup()
|
||||
d.generatePrivateKeyAndCert(t)
|
||||
d.convertPrivateKeyAndCertToPkcs12(t)
|
||||
d.convertPkcs12ToJceks(t)
|
||||
}
|
||||
|
||||
ks, err := Load(d.jceksFilename, []byte(d.storePassword))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key, certs, err := ks.GetPrivateKeyAndCerts(d.alias, []byte(d.keyPassword))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if key == nil {
|
||||
t.Fatal("unable to load key")
|
||||
}
|
||||
|
||||
expected, err := LoadPEMKey(d.keyFilename)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !equalRSAPrivateKey(key, expected) {
|
||||
t.Fatalf("keys are not equal")
|
||||
}
|
||||
|
||||
if len(certs) != 1 {
|
||||
t.Fatalf("unexpected number of certs: %d != 1", len(certs))
|
||||
}
|
||||
|
||||
expectedCert, err := LoadPEMCert(d.certFilename)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !certs[0].Equal(expectedCert) {
|
||||
t.Fatalf("certs are not equal")
|
||||
}
|
||||
|
||||
keyAliases := ks.ListPrivateKeys()
|
||||
if !reflect.DeepEqual(keyAliases, []string{d.alias}) {
|
||||
t.Fatalf("unexpected private key aliases: %s", keyAliases)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrustedCert(t *testing.T) {
|
||||
d := newTestData("trusted-cert")
|
||||
if *generateTestData {
|
||||
d.cleanup()
|
||||
d.generatePrivateKeyAndCert(t)
|
||||
d.importCertToJceks(t)
|
||||
}
|
||||
|
||||
ks, err := Load(d.jceksFilename, []byte(d.storePassword))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cert, err := ks.GetCert(d.alias)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cert == nil {
|
||||
t.Fatal("unable to load cert")
|
||||
}
|
||||
|
||||
expectedCert, err := LoadPEMCert(d.certFilename)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !cert.Equal(expectedCert) {
|
||||
t.Fatalf("certs are not equal")
|
||||
}
|
||||
|
||||
certAliases := ks.ListCerts()
|
||||
if !reflect.DeepEqual(certAliases, []string{d.alias}) {
|
||||
t.Fatalf("unexpected cert aliases: %s", certAliases)
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*-
|
||||
* Copyright 2016 Square Inc.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 jceks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
"crypto/des"
|
||||
"crypto/md5"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
oidPBEWithMD5AndDES3CBC = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 42, 2, 19, 1}
|
||||
)
|
||||
|
||||
type pbeParameters struct {
|
||||
Salt []byte
|
||||
Iterations int
|
||||
}
|
||||
|
||||
// Here's how this algorithm works:
|
||||
//
|
||||
// 1. Split salt in two halves. If the two halves are identical,
|
||||
// invert one of them.
|
||||
// 2. Concatenate password with each of the halves.
|
||||
// 3. Digest each concatenation with c iterations, where c is the
|
||||
// iterationCount. Concatenate the output from each digest round with the
|
||||
// password, and use the result as the input to the next digest operation.
|
||||
// The digest algorithm is MD5.
|
||||
// 4. After c iterations, use the 2 resulting digests as follows:
|
||||
// The 16 bytes of the first digest and the 1st 8 bytes of the 2nd digest
|
||||
// form the triple DES key, and the last 8 bytes of the 2nd digest form the
|
||||
// IV.
|
||||
func recoverPBEWithMD5AndDES3CBC(
|
||||
algo pkix.AlgorithmIdentifier, encryptedKey, password []byte) ([]byte, error) {
|
||||
var params pbeParameters
|
||||
if _, err := asn1.Unmarshal(algo.Parameters.FullBytes, ¶ms); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert password to byte array, so that it can be digested.
|
||||
passwdBytes := make([]byte, len(password))
|
||||
for i := 0; i < len(password); i++ {
|
||||
passwdBytes[i] = password[i] & 0x7f
|
||||
}
|
||||
|
||||
salt := params.Salt
|
||||
if len(salt) != 8 {
|
||||
return nil, fmt.Errorf("unexpected salt length: %d", len(salt))
|
||||
}
|
||||
|
||||
if bytes.Compare(salt[0:4], salt[4:]) == 0 {
|
||||
// First and second half of salt are equal, invert first half.
|
||||
for i := 0; i < 2; i++ {
|
||||
salt[i], salt[3-i] = salt[3-i], salt[i]
|
||||
}
|
||||
}
|
||||
|
||||
const keyLen = 24
|
||||
const blockSize = des.BlockSize
|
||||
derivedKey := make([]byte, keyLen+blockSize)
|
||||
// Now digest each half (concatenated with password). For each
|
||||
// half, go through the loop as many times as specified by the
|
||||
// iteration count parameter (inner for loop). Concatenate the
|
||||
// output from each digest round with the password, and use the
|
||||
// result as the input to the next digest operation.
|
||||
md := md5.New()
|
||||
for i := 0; i < 2; i++ {
|
||||
n := len(salt) / 2
|
||||
toBeHashed := salt[i*n : (i+1)*n]
|
||||
for j := 0; j < params.Iterations; j++ {
|
||||
md.Write(toBeHashed)
|
||||
md.Write(passwdBytes)
|
||||
toBeHashed = md.Sum([]byte{})
|
||||
md.Reset()
|
||||
}
|
||||
copy(derivedKey[i*len(toBeHashed):], toBeHashed)
|
||||
}
|
||||
|
||||
cipherKey := derivedKey[0:keyLen]
|
||||
iv := derivedKey[keyLen:]
|
||||
|
||||
des3, err := des.NewTripleDESCipher(cipherKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decrypter := cipher.NewCBCDecrypter(des3, iv)
|
||||
if (len(encryptedKey) % decrypter.BlockSize()) != 0 {
|
||||
return nil, fmt.Errorf("encrypted data must be a multiple of block length: %d %d",
|
||||
len(encryptedKey), decrypter.BlockSize())
|
||||
}
|
||||
|
||||
decryptedKey := make([]byte, len(encryptedKey))
|
||||
decrypter.CryptBlocks(decryptedKey, encryptedKey)
|
||||
return decryptedKey, nil
|
||||
}
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDFzCCAf+gAwIBAgIJALRGD3GBYHSZMA0GCSqGSIb3DQEBBQUAMD0xEjAQBgNV
|
||||
BAMTCVRlc3QgVXNlcjEaMBgGA1UEChMRVGVzdCBPcmdhbml6YXRpb24xCzAJBgNV
|
||||
BAYTAlVTMB4XDTE0MDMxNDE0MTA0NFoXDTE1MDMxNDE0MTA0NFowPTESMBAGA1UE
|
||||
AxMJVGVzdCBVc2VyMRowGAYDVQQKExFUZXN0IE9yZ2FuaXphdGlvbjELMAkGA1UE
|
||||
BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDjX4e65+mxX5Ha
|
||||
SbSHwluBy9v7+pJrOuEco+w1dFjw7g5ijjB/5HW72N9J8LfQrEAMX9FuMue7lE1y
|
||||
GcgL9Kepuz71jPjyjrHpFNa90dKzrhwkQ6f9aLkKd5D+84ua7/O7/l5yiuD+slvm
|
||||
dppj+WxfWfQ58bgFjoxwkt2voYa/+pYK52CkykaQ0jtfEIdbftRAT2rLVSD6UljT
|
||||
9t54xUL80CHgog8nlySTFdpPnTQ2jpYBDziRpO6Agjz6zjOo2tiY+yo/2QL3rhzQ
|
||||
e8zYVjH+jZ3hKAeQp5kOTdfjkxJzQe3LLm0pQCkaWhglcnZhSqdErVScNmv87nR8
|
||||
CEXXK9O/AgMBAAGjGjAYMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMA0GCSqGSIb3
|
||||
DQEBBQUAA4IBAQAzbdBJFsYq2Rrh0xwHuYTcA/NesrV1wC36kCcVbFI3JVqZ2lZz
|
||||
MlA89mJ8oOfH2Cg0hB9XlQoysdYiKXwl9tJP6lOimSp2nSn0NOKBaSvvpUyMBocn
|
||||
29L1IPm6BrauGGDhKxByA3sPc/FSZnCx2p1OO9HG3vAprYJppiiOSTwuRHO4+PBj
|
||||
gowPrvXGucKHfNlekxN1D6dYi+Zg+61pA/6qDJ+ZKXVECE3NN3H/o6WuOIB5tFVE
|
||||
tCJNSxo/8KnrU1TMG1e9EvcL3kq3rmXEID6pwmxqg3piKg3Dar/GkpxGmdB306UF
|
||||
HnRjsjYEj+HWOpfOy1vPbbKMXtC3TayPyGZ6
|
||||
-----END CERTIFICATE-----
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA41+HuufpsV+R2km0h8Jbgcvb+/qSazrhHKPsNXRY8O4OYo4w
|
||||
f+R1u9jfSfC30KxADF/RbjLnu5RNchnIC/Snqbs+9Yz48o6x6RTWvdHSs64cJEOn
|
||||
/Wi5CneQ/vOLmu/zu/5ecorg/rJb5naaY/lsX1n0OfG4BY6McJLdr6GGv/qWCudg
|
||||
pMpGkNI7XxCHW37UQE9qy1Ug+lJY0/beeMVC/NAh4KIPJ5ckkxXaT500No6WAQ84
|
||||
kaTugII8+s4zqNrYmPsqP9kC964c0HvM2FYx/o2d4SgHkKeZDk3X45MSc0Htyy5t
|
||||
KUApGloYJXJ2YUqnRK1UnDZr/O50fAhF1yvTvwIDAQABAoIBAGnboCW0s2iRRiaL
|
||||
CjHqmw/jCZhXILQrxYLADskUUhRZwPjBmnLwup+qaMrT98B/cZJRSgA0Uw9SDHyC
|
||||
5FAsp6KuOrG34G+NX+dUfGYDukVNWmzH0v54My5cXHVWjjikXqW31+EcJ4RtJbw2
|
||||
m8rP5VS/XKVdlH+BzoDa37tVSJc4AraA3XHaU6ebuWojSiHJ3T1L1gTZKmOIL2dw
|
||||
ETIgxKfQTVwceWBmQv61pG5bxqIsmDpeCWjeULchSvuiHG0xRfzJYewhv8LKVpEJ
|
||||
/bSb5kciAm+nnigme2kjsp05AGmrncv7XYRrn1caJc30kwGsbv5Y7UJ9ZG5amlL6
|
||||
ltBxSQECgYEA+iqKyEdf/3qORker/WvHEL54mrbfSYoB3B6mr7uKb/JuIOk1yYX5
|
||||
Fb0xDavxypBvwAUTNpRjjMRr1HpKggfv4ARudgjBxjD5Sy1TaBypYlB2gVuPRHZv
|
||||
TLbM7erVQVsg2jC36nQboO8ZiKiSpFlZiAn0ieCxmE9ixATzOJp9lwcCgYEA6Kzq
|
||||
uX/8BMFLmcHodpAakvxTfv3qGgz0GpSQAmu6bo4qyWuYH4hiBe8PDzCfSfwkhWFr
|
||||
Q06s9rKseSyAzqF5YBjq8+8bKjZyBaC8m4QEpgocZpwQqKicrnngh7LWiYVNLfw/
|
||||
qPfrLc0RmgZistWbES1/6Pm3x+Z49PLyVcUAt4kCgYB4RYO7jjUlCrLkLwkNKYfn
|
||||
EOvC1jC7llIWldXlnvCLqa4wvG5TmMmMHg07WXNBw/c2BjqafvTtdHGzEahIo7A7
|
||||
r2W78bHXqyvvbLcw0rbMwYp33qEedSJFa41SxRgJ99nvjISff3rZAJryDLmTsjFN
|
||||
KhwbPZ+kbmY5f3e/uuaueQKBgDkfyvkD/QHF2yPCwanqMzwHCxDQkhsXNw8Xjkup
|
||||
2zmtWb/d1JlZSIega5gVHeZyKx08D7OUq05eC44saOtSJZR8SaLd/1Nbzp6nGecs
|
||||
gF+rd9GRW12tF9qWPZPTSmy093/kwFRhmbHC+SFRlAXH/6w1+YNfW8mOQgARbYG1
|
||||
PjnRAoGBAIbnGfWSLl6jlo1W9CVk8AylY3TFmVygbijjrK3UYL/3KLJn6nb2BeW0
|
||||
IsigU5qe+za2zW2KDkw//9vqm0omda66ubwU24uXjKwhZbrAAhmy8kFZNRzpMZIF
|
||||
Q3RqsGxPxWt7Rk79OBxtnPLWc8nxpGTliOUzgeZqnLy1aSAZnngn
|
||||
-----END RSA PRIVATE KEY-----
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDFzCCAf+gAwIBAgIJANpdtwWQVU6qMA0GCSqGSIb3DQEBBQUAMD0xEjAQBgNV
|
||||
BAMTCVRlc3QgVXNlcjEaMBgGA1UEChMRVGVzdCBPcmdhbml6YXRpb24xCzAJBgNV
|
||||
BAYTAlVTMB4XDTE0MDMxNDE0MTA0NVoXDTE1MDMxNDE0MTA0NVowPTESMBAGA1UE
|
||||
AxMJVGVzdCBVc2VyMRowGAYDVQQKExFUZXN0IE9yZ2FuaXphdGlvbjELMAkGA1UE
|
||||
BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTX69McLtRMUhP
|
||||
ZSKzyTee1kY6XUyiHqEapxhKxTkT5HyQaKspIeP60keOiCn6y/stwXK/4fjm4XMx
|
||||
jDIw2HE6/gwLBXPr3IH3x44XXjK9U3SGSCrFhP0aI4dNxLq7oKhzfR15Wz3pw4zY
|
||||
q3mtT4muefqjkcVUNTwJyzxA5D5km1Coq7xKZ9Demt71NUcAYOfJswFh97bI8QpK
|
||||
MfaYbUSWw0um+Mz2vlL20Yq2xWjX+CgclnVWjsf/zrbXkuGyWPE1VbTCzwvdbUHW
|
||||
8cokI+UBQC/FGIxVq11hGn+Tk8cMubnDLEqRf3oiCGx0wvKixP55YlthRElSaNKt
|
||||
DmduKH2hAgMBAAGjGjAYMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMA0GCSqGSIb3
|
||||
DQEBBQUAA4IBAQAY6BFSl2zjhsD2udgb7mgOFgCwzWzCis5+Oy+2H/uNjyeBuSgM
|
||||
qrh779y+BRapxrqBSYo8QWltapAlRJ8MwIYJpHz2FqN4bY38r3WMZ380wVT/wDYh
|
||||
6WFB3REVKEkhsW2HWX4cOF8m2iD/WOR2x2T0F5EC4W1nofl/oIhgujDNjmerZEMF
|
||||
JPnXHGRNOpYtyZhWuo+CFh500vV9Azomcw3z3NJ4teW5Z2T0mmOQWLB1cc1rAVYB
|
||||
W9uqE5GyHcQ6VHdQHjq3luxOvzH88slQ2S/SC9a41l1Z6EAe8wlVEa17wTQ3PR2g
|
||||
UHFTtH5OUR9neGEx+G+GqnR7jfRAPZNv0DsH
|
||||
-----END CERTIFICATE-----
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA01+vTHC7UTFIT2Uis8k3ntZGOl1Moh6hGqcYSsU5E+R8kGir
|
||||
KSHj+tJHjogp+sv7LcFyv+H45uFzMYwyMNhxOv4MCwVz69yB98eOF14yvVN0hkgq
|
||||
xYT9GiOHTcS6u6Coc30deVs96cOM2Kt5rU+Jrnn6o5HFVDU8Ccs8QOQ+ZJtQqKu8
|
||||
SmfQ3pre9TVHAGDnybMBYfe2yPEKSjH2mG1ElsNLpvjM9r5S9tGKtsVo1/goHJZ1
|
||||
Vo7H/86215LhsljxNVW0ws8L3W1B1vHKJCPlAUAvxRiMVatdYRp/k5PHDLm5wyxK
|
||||
kX96IghsdMLyosT+eWJbYURJUmjSrQ5nbih9oQIDAQABAoIBAQCV28Ds8P/dEJOz
|
||||
toBj9sT4V/JybrNmPVD7FHykhi9xawzlVVAEWYLI0UzqQJ+CsBvk1MIGSK+vASgq
|
||||
eLsc5ldg+7yOE8+b6To78b9L0f0nPYPfsEqivyay4X2MJW4+mCjVuF6tK4M5uOqi
|
||||
svARb9KtYM3SKgc9LIDkcLLHTwrtR+ZRZNQ0rSXuE2dSJMETCXCdzuSaoQfoOThM
|
||||
Gmv6Sm04PVws5x2BQso9oThQHZnwCquSaveLMPguxnwanRKvCQkNvtnGzAeFQ7o3
|
||||
Lx5Zag4/4L5XLh0QT9+mWH8oRSFysPIk2pMhqzAAUxVjpo0RKmNYw7wEgYdwsXJl
|
||||
pC2X+iABAoGBAPGroa7gPgDYCzcf7tNYOTz/nDbwUXsv4B0bCsUId+71ExnVwqxI
|
||||
ubqYB0v23qjX+679CC1qsMb38//CBNlwruyLjxaDct3UFo3IfiXY1hAeN+Anvzeb
|
||||
gfoD3s2XGHPIG5niViHIEwZKubpzsQ6UXl8HSFBCPFD1piXJ7qs/zeHJAoGBAN/o
|
||||
LGbWww3vrYKZ+BCd3wIFrxurco94RDq7CfhTAinkc26WTmagyw4kXmZS0mtL8ycn
|
||||
5PhIvCJaugnEUG1kEWDsYH57GMBG/BVoJC8to0yLrVh5fVzYl343i+KkqA3Y99bE
|
||||
NFoshK+L1JH3klPhqmN7wnra/VNp2LJm4Nz+4mkZAoGAHjc5VeYPmodoj5HciGwl
|
||||
a+0BmRTe+yn3OWxiIlR2ulfF9Zr2ZhgJsLzFXMgW+sFWZICafyMxyw7BYR7fAFjI
|
||||
Zibk0wnIWNflogCJVS4RRZ6hmdMeY1N8IshNGSNlGUTRvqG/5yVey5CYPCmu34XJ
|
||||
btQ4RGCjrfOovFzNDHhDw5ECgYAE5jWigmx+L5JiWzAcXPf2OV2dg2DcVstXZaRQ
|
||||
NLDFbeRAtTU99aK7ynvuTT2hb2YAo1TVQfIr5kRP1mXUHu5qaoGqAtOF0YfOiBrS
|
||||
lXMPR7chSnc9wtd9wYVkDipHM0oo/t4OYw78MFkUYJBpGXT6/EhDG+uTGavOK4Yc
|
||||
D8+wcQKBgQDU6tlGCHK1kfc1xO6uoxHB7yLVM7lh+irLHJb0gt/zGdJUOHom+4r3
|
||||
zBW22vWWrYv2kck0ez6hvwgw1eIys6yY3Cz+8GpR0wVPxj4CiVDWfhQZeuu4rP1d
|
||||
BFGjC0y1npoI4bJE8G3QiNJYRKaHCaf2hdvNr3AhwDT6QCDo+kQCtQ==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -0,0 +1,69 @@
|
||||
/*-
|
||||
* Copyright 2016 Square Inc.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 jceks
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LoadPEMKey extracts a private key from a PEM file.
|
||||
func LoadPEMKey(filename string) (*rsa.PrivateKey, error) {
|
||||
keyPEMBlock, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var keyDERBlock *pem.Block
|
||||
for {
|
||||
keyDERBlock, keyPEMBlock = pem.Decode(keyPEMBlock)
|
||||
if keyDERBlock == nil {
|
||||
return nil, fmt.Errorf("failed to parse key PEM data")
|
||||
}
|
||||
if keyDERBlock.Type == "PRIVATE KEY" ||
|
||||
strings.HasSuffix(keyDERBlock.Type, " PRIVATE KEY") {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return x509.ParsePKCS1PrivateKey(keyDERBlock.Bytes)
|
||||
}
|
||||
|
||||
// LoadPEMCert extracts a certificate from a PEM file.
|
||||
func LoadPEMCert(filename string) (*x509.Certificate, error) {
|
||||
certPEMBlock, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var certDERBlock *pem.Block
|
||||
for {
|
||||
certDERBlock, certPEMBlock = pem.Decode(certPEMBlock)
|
||||
if certDERBlock == nil {
|
||||
return nil, fmt.Errorf("failed to parse certificate PEM data")
|
||||
}
|
||||
if certDERBlock.Type == "CERTIFICATE" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return x509.ParseCertificate(certDERBlock.Bytes)
|
||||
}
|
||||
Reference in New Issue
Block a user