326 lines
9.2 KiB
Go
326 lines
9.2 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package crypto
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/hyperledger/fabric-protos-go/msp"
|
|
"github.com/hyperledger/fabric/common/crypto/tlsgen"
|
|
"github.com/hyperledger/fabric/protoutil"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestX509CertExpiresAt(t *testing.T) {
|
|
certBytes, err := ioutil.ReadFile(filepath.Join("testdata", "cert.pem"))
|
|
require.NoError(t, err)
|
|
sId := &msp.SerializedIdentity{
|
|
IdBytes: certBytes,
|
|
}
|
|
serializedIdentity, err := proto.Marshal(sId)
|
|
require.NoError(t, err)
|
|
expirationTime := ExpiresAt(serializedIdentity)
|
|
require.Equal(t, time.Date(2027, 8, 17, 12, 19, 48, 0, time.UTC), expirationTime)
|
|
}
|
|
|
|
func TestX509InvalidCertExpiresAt(t *testing.T) {
|
|
certBytes, err := ioutil.ReadFile(filepath.Join("testdata", "badCert.pem"))
|
|
require.NoError(t, err)
|
|
sId := &msp.SerializedIdentity{
|
|
IdBytes: certBytes,
|
|
}
|
|
serializedIdentity, err := proto.Marshal(sId)
|
|
require.NoError(t, err)
|
|
expirationTime := ExpiresAt(serializedIdentity)
|
|
require.True(t, expirationTime.IsZero())
|
|
}
|
|
|
|
func TestIdemixIdentityExpiresAt(t *testing.T) {
|
|
idemixId := &msp.SerializedIdemixIdentity{
|
|
NymX: []byte{1, 2, 3},
|
|
NymY: []byte{1, 2, 3},
|
|
Ou: []byte("OU1"),
|
|
}
|
|
idemixBytes, err := proto.Marshal(idemixId)
|
|
require.NoError(t, err)
|
|
sId := &msp.SerializedIdentity{
|
|
IdBytes: idemixBytes,
|
|
}
|
|
serializedIdentity, err := proto.Marshal(sId)
|
|
require.NoError(t, err)
|
|
expirationTime := ExpiresAt(serializedIdentity)
|
|
require.True(t, expirationTime.IsZero())
|
|
}
|
|
|
|
func TestInvalidIdentityExpiresAt(t *testing.T) {
|
|
expirationTime := ExpiresAt([]byte{1, 2, 3})
|
|
require.True(t, expirationTime.IsZero())
|
|
}
|
|
|
|
func TestTrackExpiration(t *testing.T) {
|
|
ca, err := tlsgen.NewCA()
|
|
require.NoError(t, err)
|
|
|
|
now := time.Now()
|
|
expirationTime := certExpirationTime(ca.CertBytes())
|
|
|
|
timeUntilExpiration := expirationTime.Sub(now)
|
|
timeUntilOneMonthBeforeExpiration := timeUntilExpiration - 28*24*time.Hour
|
|
timeUntil2DaysBeforeExpiration := timeUntilExpiration - 2*24*time.Hour - time.Hour*12
|
|
|
|
monthBeforeExpiration := now.Add(timeUntilOneMonthBeforeExpiration)
|
|
twoDaysBeforeExpiration := now.Add(timeUntil2DaysBeforeExpiration)
|
|
|
|
tlsCert, err := ca.NewServerCertKeyPair("127.0.0.1")
|
|
require.NoError(t, err)
|
|
|
|
signingIdentity := protoutil.MarshalOrPanic(&msp.SerializedIdentity{
|
|
IdBytes: tlsCert.Cert,
|
|
})
|
|
|
|
warnShouldNotBeInvoked := func(format string, args ...interface{}) {
|
|
t.Fatalf(format, args...)
|
|
}
|
|
|
|
var formattedWarning string
|
|
warnShouldBeInvoked := func(format string, args ...interface{}) {
|
|
formattedWarning = fmt.Sprintf(format, args...)
|
|
}
|
|
|
|
var formattedInfo string
|
|
infoShouldBeInvoked := func(format string, args ...interface{}) {
|
|
formattedInfo = fmt.Sprintf(format, args...)
|
|
}
|
|
|
|
for _, testCase := range []struct {
|
|
description string
|
|
tls bool
|
|
serverCert []byte
|
|
clientCertChain [][]byte
|
|
sIDBytes []byte
|
|
info MessageFunc
|
|
warn MessageFunc
|
|
now time.Time
|
|
expectedInfoPrefix string
|
|
expectedWarn string
|
|
}{
|
|
{
|
|
description: "No TLS, enrollment cert isn't valid logs a warning",
|
|
warn: warnShouldNotBeInvoked,
|
|
sIDBytes: []byte{1, 2, 3},
|
|
},
|
|
{
|
|
description: "No TLS, enrollment cert expires soon",
|
|
sIDBytes: signingIdentity,
|
|
info: infoShouldBeInvoked,
|
|
warn: warnShouldBeInvoked,
|
|
now: monthBeforeExpiration,
|
|
expectedInfoPrefix: "The enrollment certificate will expire on",
|
|
expectedWarn: "The enrollment certificate will expire within one week",
|
|
},
|
|
{
|
|
description: "TLS, server cert expires soon",
|
|
info: infoShouldBeInvoked,
|
|
warn: warnShouldBeInvoked,
|
|
now: monthBeforeExpiration,
|
|
tls: true,
|
|
serverCert: tlsCert.Cert,
|
|
expectedInfoPrefix: "The server TLS certificate will expire on",
|
|
expectedWarn: "The server TLS certificate will expire within one week",
|
|
},
|
|
{
|
|
description: "TLS, server cert expires really soon",
|
|
info: infoShouldBeInvoked,
|
|
warn: warnShouldBeInvoked,
|
|
now: twoDaysBeforeExpiration,
|
|
tls: true,
|
|
serverCert: tlsCert.Cert,
|
|
expectedInfoPrefix: "The server TLS certificate will expire on",
|
|
expectedWarn: "The server TLS certificate expires within 2 days and 12 hours",
|
|
},
|
|
{
|
|
description: "TLS, server cert has expired",
|
|
info: infoShouldBeInvoked,
|
|
warn: warnShouldBeInvoked,
|
|
now: expirationTime.Add(time.Hour),
|
|
tls: true,
|
|
serverCert: tlsCert.Cert,
|
|
expectedWarn: "The server TLS certificate has expired",
|
|
},
|
|
{
|
|
description: "TLS, client cert expires soon",
|
|
info: infoShouldBeInvoked,
|
|
warn: warnShouldBeInvoked,
|
|
now: monthBeforeExpiration,
|
|
tls: true,
|
|
clientCertChain: [][]byte{tlsCert.Cert},
|
|
expectedInfoPrefix: "The client TLS certificate will expire on",
|
|
expectedWarn: "The client TLS certificate will expire within one week",
|
|
},
|
|
} {
|
|
t.Run(testCase.description, func(t *testing.T) {
|
|
defer func() {
|
|
formattedWarning = ""
|
|
formattedInfo = ""
|
|
}()
|
|
|
|
fakeTimeAfter := func(duration time.Duration, f func()) *time.Timer {
|
|
require.NotEmpty(t, testCase.expectedWarn)
|
|
threeWeeks := 3 * 7 * 24 * time.Hour
|
|
require.Equal(t, threeWeeks, duration)
|
|
f()
|
|
return nil
|
|
}
|
|
|
|
TrackExpiration(testCase.tls,
|
|
testCase.serverCert,
|
|
testCase.clientCertChain,
|
|
testCase.sIDBytes,
|
|
testCase.info,
|
|
testCase.warn,
|
|
testCase.now,
|
|
fakeTimeAfter)
|
|
|
|
if testCase.expectedInfoPrefix != "" {
|
|
require.True(t, strings.HasPrefix(formattedInfo, testCase.expectedInfoPrefix))
|
|
} else {
|
|
require.Empty(t, formattedInfo)
|
|
}
|
|
|
|
if testCase.expectedWarn != "" {
|
|
require.Equal(t, testCase.expectedWarn, formattedWarning)
|
|
} else {
|
|
require.Empty(t, formattedWarning)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLogNonPubKeyMismatchErr(t *testing.T) {
|
|
ca, err := tlsgen.NewCA()
|
|
require.NoError(t, err)
|
|
|
|
aliceKeyPair, err := ca.NewClientCertKeyPair()
|
|
require.NoError(t, err)
|
|
|
|
bobKeyPair, err := ca.NewClientCertKeyPair()
|
|
require.NoError(t, err)
|
|
|
|
expected := &bytes.Buffer{}
|
|
expected.WriteString(fmt.Sprintf("Failed determining if public key of %s matches public key of %s: foo",
|
|
string(aliceKeyPair.Cert),
|
|
string(bobKeyPair.Cert)))
|
|
|
|
b := &bytes.Buffer{}
|
|
f := func(template string, args ...interface{}) {
|
|
fmt.Fprintf(b, template, args...)
|
|
}
|
|
|
|
LogNonPubKeyMismatchErr(f, errors.New("foo"), aliceKeyPair.TLSCert.Raw, bobKeyPair.TLSCert.Raw)
|
|
|
|
require.Equal(t, expected.String(), b.String())
|
|
}
|
|
|
|
func TestCertificatesWithSamePublicKey(t *testing.T) {
|
|
ca, err := tlsgen.NewCA()
|
|
require.NoError(t, err)
|
|
|
|
bobKeyPair, err := ca.NewClientCertKeyPair()
|
|
require.NoError(t, err)
|
|
|
|
bobCert := bobKeyPair.Cert
|
|
bob := pem2der(bobCert)
|
|
|
|
aliceCert := `-----BEGIN CERTIFICATE-----
|
|
MIIBNjCB3KADAgECAgELMAoGCCqGSM49BAMCMBAxDjAMBgNVBAUTBUFsaWNlMB4X
|
|
DTIwMDgxODIxMzU1NFoXDTIwMDgyMDIxMzU1NFowEDEOMAwGA1UEBRMFQWxpY2Uw
|
|
WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQjZP5VD/RaczoPFbA4gkt1qb54R6SP
|
|
J/V5oxkhDboG9xWi0wpyghaMGwwxC7Q9wegEnyOVp9nXoLrQ8LUJ5BfZoycwJTAO
|
|
BgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwCgYIKoZIzj0EAwID
|
|
SQAwRgIhAK4le5XgH5edyhaQ9Sz7sFz3Zc4bbhPAzt9zQUYnoqK+AiEA5zcyLB/4
|
|
Oqe93lroE6GF9W7UoCZFzD7lXsWku/dgFOU=
|
|
-----END CERTIFICATE-----`
|
|
|
|
reIssuedAliceCert := `-----BEGIN CERTIFICATE-----
|
|
MIIBNDCB3KADAgECAgELMAoGCCqGSM49BAMCMBAxDjAMBgNVBAUTBUFsaWNlMB4X
|
|
DTIwMDgxODIxMzY1NFoXDTIwMDgyMDIxMzY1NFowEDEOMAwGA1UEBRMFQWxpY2Uw
|
|
WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQjZP5VD/RaczoPFbA4gkt1qb54R6SP
|
|
J/V5oxkhDboG9xWi0wpyghaMGwwxC7Q9wegEnyOVp9nXoLrQ8LUJ5BfZoycwJTAO
|
|
BgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwCgYIKoZIzj0EAwID
|
|
RwAwRAIgDc8WyXFvsxCk97KS7D/LdYJxMpDKdHNFqpzJT9LddlsCIEr8KcMd/t5p
|
|
cRv6rqxvy5M+t0DhRtiwCen70YCUsksb
|
|
-----END CERTIFICATE-----`
|
|
|
|
alice := pem2der([]byte(aliceCert))
|
|
aliceMakesComeback := pem2der([]byte(reIssuedAliceCert))
|
|
|
|
for _, test := range []struct {
|
|
description string
|
|
errContains string
|
|
first []byte
|
|
second []byte
|
|
}{
|
|
{
|
|
description: "Bad first certificate",
|
|
errContains: "malformed certificate",
|
|
first: []byte{1, 2, 3},
|
|
second: bob,
|
|
},
|
|
|
|
{
|
|
description: "Bad second certificate",
|
|
errContains: "malformed certificate",
|
|
first: alice,
|
|
second: []byte{1, 2, 3},
|
|
},
|
|
|
|
{
|
|
description: "Different certificate",
|
|
errContains: ErrPubKeyMismatch.Error(),
|
|
first: alice,
|
|
second: bob,
|
|
},
|
|
|
|
{
|
|
description: "Same certificate",
|
|
first: alice,
|
|
second: alice,
|
|
},
|
|
|
|
{
|
|
description: "Same certificate but different validity period",
|
|
first: alice,
|
|
second: aliceMakesComeback,
|
|
},
|
|
} {
|
|
t.Run(test.description, func(t *testing.T) {
|
|
err := CertificatesWithSamePublicKey(test.first, test.second)
|
|
if test.errContains != "" {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), test.errContains)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func pem2der(p []byte) []byte {
|
|
b, _ := pem.Decode(p)
|
|
return b.Bytes
|
|
}
|