Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions certtostore_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -1825,3 +1825,45 @@ func (w *WinCertStore) CertByCommonName(commonName string) (*x509.Certificate,
}
return nil, nil, nil, cryptENotFound
}

// ListCertificates retrieves all certificates from the Windows MY certificate store.
// It enumerates all certificates found within it.
//
// Returns an error if the certificate store cannot be opened or if there are issues
// during the certificate enumeration process.
func (w *WinCertStore) ListCertificates() ([]*x509.Certificate, error) {
storeHandle, err := w.storeHandle(w.storeDomain(), my)
if err != nil {
return nil, fmt.Errorf("failed to open certificate store: %v", err)
}

certs := []*x509.Certificate{}

var certContext *windows.CertContext
for {
var cert *x509.Certificate
certContext, err = findCert(
storeHandle,
encodingX509ASN|encodingPKCS7,
0,
windows.CERT_FIND_ANY,
nil,
certContext,
)
if err != nil {
return nil, fmt.Errorf("could not find certificates: %w", err)
}
if certContext == nil {
break // No more certificates found
}
cert, err = certContextToX509(certContext)
if err != nil {
// Continue enumeration even if this cert can't be converted
continue
}

certs = append(certs, cert)
}

return certs, nil
}
162 changes: 162 additions & 0 deletions certtostore_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -676,3 +676,165 @@ func TestCertByCommonName(t *testing.T) {
t.Errorf("chains[0][0] is not the leaf; got %v, want leaf %v", chains[0][0].Subject, found.Subject)
}
}

func TestListCertificates(t *testing.T) {
// Insert test certificates directly into the current-user MY store to avoid private key association.
// Local constants for CertOpenStore.
const (
certStoreProvSystem = 10 // CERT_STORE_PROV_SYSTEM
certSystemStoreCurrentUser = 1 << 16
x509ASN = 1 // X509_ASN_ENCODING
pkcs7ASN = 65536 // PKCS_7_ASN_ENCODING
)
myW, err := windows.UTF16PtrFromString("MY")
if err != nil {
t.Fatalf("UTF16PtrFromString: %v", err)
}

h, err := windows.CertOpenStore(
certStoreProvSystem,
0,
0,
certSystemStoreCurrentUser,
uintptr(unsafe.Pointer(myW)),
)
if err != nil {
t.Fatalf("CertOpenStore: %v", err)
}
defer windows.CertCloseStore(h, 0)

// Create two self-signed certs with unique CNs.
cert1CN := fmt.Sprintf("__certtostore_test1_%d__", time.Now().UnixNano())
cert2CN := fmt.Sprintf("__certtostore_test2_%d__", time.Now().UnixNano())

// Helper function to create a certificate
createCert := func(cn string) error {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
template := x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano()),
Subject: pkix.Name{
CommonName: cn,
},
NotBefore: time.Now().Add(-1 * time.Minute),
NotAfter: time.Now().Add(5 * time.Minute),
KeyUsage: x509.KeyUsageDigitalSignature,
BasicConstraintsValid: true,
}
der, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
if err != nil {
return err
}

cert, err := x509.ParseCertificate(der)
if err != nil {
return err
}

// Add certificate to store
ctx, err := windows.CertCreateCertificateContext(
x509ASN|pkcs7ASN,
&cert.Raw[0],
uint32(len(cert.Raw)),
)
if err != nil {
return fmt.Errorf("CertCreateCertificateContext for cert %q: %v", cn, err)
}
defer windows.CertFreeCertificateContext(ctx)

if err := windows.CertAddCertificateContextToStore(h, ctx, windows.CERT_STORE_ADD_ALWAYS, nil); err != nil {
return fmt.Errorf("CertAddCertificateContextToStore for cert %q: %v", cn, err)
}

return nil
}

// Helper function to remove certificate by common name
removeCertByCN := func(cn string) error {
cnW, err := windows.UTF16PtrFromString(cn)
if err != nil {
return err
}

ctx, err := windows.CertFindCertificateInStore(
h,
x509ASN|pkcs7ASN,
0,
windows.CERT_FIND_SUBJECT_STR,
unsafe.Pointer(cnW),
nil,
)
if err != nil {
return err
}
if ctx == nil {
return nil // Certificate not found, nothing to remove
}

// RemoveCertByContext will free the context
return RemoveCertByContext(ctx)
}

// Create test certificates
if err := createCert(cert1CN); err != nil {
t.Fatalf("failed to create cert1: %v", err)
}
defer func() {
if delErr := removeCertByCN(cert1CN); delErr != nil {
t.Logf("Failed to remove cert1: %v", delErr)
}
}()

if err := createCert(cert2CN); err != nil {
t.Fatalf("failed to create cert2: %v", err)
}
defer func() {
if delErr := removeCertByCN(cert2CN); delErr != nil {
t.Logf("Failed to remove cert2: %v", delErr)
}
}()

// Open the store
opts := WinCertStoreOptions{
Provider: ProviderMSSoftware,
Container: "TestContainerForListCertificates",
CurrentUser: true,
}
store, err := OpenWinCertStoreWithOptions(opts)
if err != nil {
t.Fatalf("failed to open store: %v", err)
}
defer store.Close()

// Now test ListCertificates
certs, err := store.ListCertificates()
if err != nil {
t.Fatalf("ListCertificates failed: %v", err)
}

// Test that ListCertificates returns valid x509 certificates
var cert1Found, cert2Found bool
for i, cert := range certs {
if cert == nil {
t.Errorf("certificate at index %d is nil", i)
continue
}

// Check that we got cert1 and cert2 returned
cert1Found = cert1Found || cert.Subject.CommonName == cert1CN
cert2Found = cert2Found || cert.Subject.CommonName == cert2CN

// Log certificate details for debugging
t.Logf("Certificate %d: CN=%s, ValidFrom=%s, ValidTo=%s", i, cert.Subject.CommonName, cert.NotBefore, cert.NotAfter)
}

if !cert1Found {
t.Errorf("cert1 with CN %s not found in ListCertificates output", cert1CN)
}

if !cert2Found {
t.Errorf("cert2 with CN %s not found in ListCertificates output", cert2CN)
}
}