Skip to content

Commit d12e922

Browse files
committed
Add comprehensive CREATE CERTIFICATE parsing
Support source types (FROM ASSEMBLY, FROM FILE, FROM EXECUTABLE FILE), AUTHORIZATION, WITH PRIVATE KEY options (FILE, DECRYPTION BY PASSWORD, ENCRYPTION BY PASSWORD), certificate options (SUBJECT, START_DATE, EXPIRY_DATE), and ACTIVE FOR BEGIN_DIALOG.
1 parent aa6a1d0 commit d12e922

5 files changed

Lines changed: 267 additions & 6 deletions

File tree

ast/create_simple_statements.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,44 @@ func (s *CreateAssemblyStatement) statement() {}
122122

123123
// CreateCertificateStatement represents a CREATE CERTIFICATE statement.
124124
type CreateCertificateStatement struct {
125-
Name *Identifier `json:"Name,omitempty"`
125+
Name *Identifier `json:"Name,omitempty"`
126+
Owner *Identifier `json:"Owner,omitempty"`
127+
CertificateSource EncryptionSource `json:"CertificateSource,omitempty"`
128+
ActiveForBeginDialog string `json:"ActiveForBeginDialog,omitempty"` // "On", "Off", "NotSet"
129+
PrivateKeyPath *StringLiteral `json:"PrivateKeyPath,omitempty"`
130+
EncryptionPassword *StringLiteral `json:"EncryptionPassword,omitempty"`
131+
DecryptionPassword *StringLiteral `json:"DecryptionPassword,omitempty"`
132+
CertificateOptions []*CertificateOption `json:"CertificateOptions,omitempty"`
126133
}
127134

128135
func (s *CreateCertificateStatement) node() {}
129136
func (s *CreateCertificateStatement) statement() {}
130137

138+
// CertificateOption represents an option in a CREATE CERTIFICATE statement.
139+
type CertificateOption struct {
140+
Kind string `json:"Kind,omitempty"` // "Subject", "StartDate", "ExpiryDate"
141+
Value *StringLiteral `json:"Value,omitempty"`
142+
}
143+
144+
func (o *CertificateOption) node() {}
145+
146+
// AssemblyEncryptionSource represents a certificate source from an assembly.
147+
type AssemblyEncryptionSource struct {
148+
Assembly *Identifier `json:"Assembly,omitempty"`
149+
}
150+
151+
func (s *AssemblyEncryptionSource) node() {}
152+
func (s *AssemblyEncryptionSource) encryptionSource() {}
153+
154+
// FileEncryptionSource represents a certificate source from a file.
155+
type FileEncryptionSource struct {
156+
IsExecutable bool `json:"IsExecutable,omitempty"`
157+
File *StringLiteral `json:"File,omitempty"`
158+
}
159+
160+
func (s *FileEncryptionSource) node() {}
161+
func (s *FileEncryptionSource) encryptionSource() {}
162+
131163
// CreateAsymmetricKeyStatement represents a CREATE ASYMMETRIC KEY statement.
132164
type CreateAsymmetricKeyStatement struct {
133165
Name *Identifier `json:"Name,omitempty"`

parser/marshal.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11876,6 +11876,23 @@ func encryptionSourceToJSON(source ast.EncryptionSource) interface{} {
1187611876
switch s := source.(type) {
1187711877
case *ast.ProviderEncryptionSource:
1187811878
return providerEncryptionSourceToJSON(s)
11879+
case *ast.AssemblyEncryptionSource:
11880+
node := jsonNode{
11881+
"$type": "AssemblyEncryptionSource",
11882+
}
11883+
if s.Assembly != nil {
11884+
node["Assembly"] = identifierToJSON(s.Assembly)
11885+
}
11886+
return node
11887+
case *ast.FileEncryptionSource:
11888+
node := jsonNode{
11889+
"$type": "FileEncryptionSource",
11890+
"IsExecutable": s.IsExecutable,
11891+
}
11892+
if s.File != nil {
11893+
node["File"] = stringLiteralToJSON(s.File)
11894+
}
11895+
return node
1187911896
default:
1188011897
return nil
1188111898
}
@@ -11992,9 +12009,38 @@ func createCertificateStatementToJSON(s *ast.CreateCertificateStatement) jsonNod
1199212009
node := jsonNode{
1199312010
"$type": "CreateCertificateStatement",
1199412011
}
12012+
if s.CertificateSource != nil {
12013+
node["CertificateSource"] = encryptionSourceToJSON(s.CertificateSource)
12014+
}
12015+
if len(s.CertificateOptions) > 0 {
12016+
options := make([]jsonNode, len(s.CertificateOptions))
12017+
for i, opt := range s.CertificateOptions {
12018+
options[i] = jsonNode{
12019+
"$type": "CertificateOption",
12020+
"Kind": opt.Kind,
12021+
"Value": stringLiteralToJSON(opt.Value),
12022+
}
12023+
}
12024+
node["CertificateOptions"] = options
12025+
}
12026+
if s.Owner != nil {
12027+
node["Owner"] = identifierToJSON(s.Owner)
12028+
}
1199512029
if s.Name != nil {
1199612030
node["Name"] = identifierToJSON(s.Name)
1199712031
}
12032+
if s.ActiveForBeginDialog != "" {
12033+
node["ActiveForBeginDialog"] = s.ActiveForBeginDialog
12034+
}
12035+
if s.PrivateKeyPath != nil {
12036+
node["PrivateKeyPath"] = stringLiteralToJSON(s.PrivateKeyPath)
12037+
}
12038+
if s.EncryptionPassword != nil {
12039+
node["EncryptionPassword"] = stringLiteralToJSON(s.EncryptionPassword)
12040+
}
12041+
if s.DecryptionPassword != nil {
12042+
node["DecryptionPassword"] = stringLiteralToJSON(s.DecryptionPassword)
12043+
}
1199812044
return node
1199912045
}
1200012046

parser/parse_statements.go

Lines changed: 186 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8676,11 +8676,194 @@ func (p *Parser) parseCreateCertificateStatement() (*ast.CreateCertificateStatem
86768676
p.nextToken() // consume CERTIFICATE
86778677

86788678
stmt := &ast.CreateCertificateStatement{
8679-
Name: p.parseIdentifier(),
8679+
Name: p.parseIdentifier(),
8680+
ActiveForBeginDialog: "NotSet",
8681+
}
8682+
8683+
// Optional AUTHORIZATION
8684+
if strings.ToUpper(p.curTok.Literal) == "AUTHORIZATION" {
8685+
p.nextToken()
8686+
stmt.Owner = p.parseIdentifier()
8687+
}
8688+
8689+
// Optional ENCRYPTION BY PASSWORD
8690+
if strings.ToUpper(p.curTok.Literal) == "ENCRYPTION" {
8691+
p.nextToken() // consume ENCRYPTION
8692+
if strings.ToUpper(p.curTok.Literal) == "BY" {
8693+
p.nextToken() // consume BY
8694+
}
8695+
if strings.ToUpper(p.curTok.Literal) == "PASSWORD" {
8696+
p.nextToken() // consume PASSWORD
8697+
if p.curTok.Type == TokenEquals {
8698+
p.nextToken()
8699+
}
8700+
if p.curTok.Type == TokenString || p.curTok.Type == TokenNationalString {
8701+
strLit, _ := p.parseStringLiteral()
8702+
stmt.EncryptionPassword = strLit
8703+
}
8704+
}
8705+
}
8706+
8707+
// Optional FROM clause
8708+
if p.curTok.Type == TokenFrom {
8709+
p.nextToken() // consume FROM
8710+
sourceType := strings.ToUpper(p.curTok.Literal)
8711+
8712+
if sourceType == "ASSEMBLY" {
8713+
p.nextToken() // consume ASSEMBLY
8714+
stmt.CertificateSource = &ast.AssemblyEncryptionSource{
8715+
Assembly: p.parseIdentifier(),
8716+
}
8717+
} else if sourceType == "FILE" || sourceType == "EXECUTABLE" {
8718+
isExecutable := false
8719+
if sourceType == "EXECUTABLE" {
8720+
isExecutable = true
8721+
p.nextToken() // consume EXECUTABLE
8722+
// Next should be FILE
8723+
}
8724+
if strings.ToUpper(p.curTok.Literal) == "FILE" {
8725+
p.nextToken() // consume FILE
8726+
if p.curTok.Type == TokenEquals {
8727+
p.nextToken()
8728+
}
8729+
if p.curTok.Type == TokenString || p.curTok.Type == TokenNationalString {
8730+
strLit, _ := p.parseStringLiteral()
8731+
stmt.CertificateSource = &ast.FileEncryptionSource{
8732+
IsExecutable: isExecutable,
8733+
File: strLit,
8734+
}
8735+
}
8736+
}
8737+
}
8738+
}
8739+
8740+
// Parse WITH clauses (can appear multiple times for different purposes)
8741+
for p.curTok.Type == TokenWith {
8742+
p.nextToken() // consume WITH
8743+
8744+
// Check if it's PRIVATE KEY or certificate options
8745+
upperLit := strings.ToUpper(p.curTok.Literal)
8746+
if upperLit == "PRIVATE" {
8747+
p.nextToken() // consume PRIVATE
8748+
if strings.ToUpper(p.curTok.Literal) == "KEY" {
8749+
p.nextToken() // consume KEY
8750+
}
8751+
if p.curTok.Type == TokenLParen {
8752+
p.nextToken() // consume (
8753+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
8754+
optName := strings.ToUpper(p.curTok.Literal)
8755+
p.nextToken() // consume option name
8756+
8757+
switch optName {
8758+
case "FILE":
8759+
if p.curTok.Type == TokenEquals {
8760+
p.nextToken()
8761+
}
8762+
if p.curTok.Type == TokenString || p.curTok.Type == TokenNationalString {
8763+
strLit, _ := p.parseStringLiteral()
8764+
stmt.PrivateKeyPath = strLit
8765+
}
8766+
case "DECRYPTION":
8767+
// DECRYPTION BY PASSWORD
8768+
if strings.ToUpper(p.curTok.Literal) == "BY" {
8769+
p.nextToken()
8770+
}
8771+
if strings.ToUpper(p.curTok.Literal) == "PASSWORD" {
8772+
p.nextToken()
8773+
if p.curTok.Type == TokenEquals {
8774+
p.nextToken()
8775+
}
8776+
if p.curTok.Type == TokenString || p.curTok.Type == TokenNationalString {
8777+
strLit, _ := p.parseStringLiteral()
8778+
stmt.DecryptionPassword = strLit
8779+
}
8780+
}
8781+
case "ENCRYPTION":
8782+
// ENCRYPTION BY PASSWORD
8783+
if strings.ToUpper(p.curTok.Literal) == "BY" {
8784+
p.nextToken()
8785+
}
8786+
if strings.ToUpper(p.curTok.Literal) == "PASSWORD" {
8787+
p.nextToken()
8788+
if p.curTok.Type == TokenEquals {
8789+
p.nextToken()
8790+
}
8791+
if p.curTok.Type == TokenString || p.curTok.Type == TokenNationalString {
8792+
strLit, _ := p.parseStringLiteral()
8793+
stmt.EncryptionPassword = strLit
8794+
}
8795+
}
8796+
}
8797+
if p.curTok.Type == TokenComma {
8798+
p.nextToken()
8799+
}
8800+
}
8801+
if p.curTok.Type == TokenRParen {
8802+
p.nextToken()
8803+
}
8804+
}
8805+
} else {
8806+
// Certificate options: SUBJECT, START_DATE, EXPIRY_DATE
8807+
for {
8808+
optName := strings.ToUpper(p.curTok.Literal)
8809+
if optName != "SUBJECT" && optName != "START_DATE" && optName != "EXPIRY_DATE" {
8810+
break
8811+
}
8812+
p.nextToken() // consume option name
8813+
if p.curTok.Type == TokenEquals {
8814+
p.nextToken()
8815+
}
8816+
if p.curTok.Type == TokenString || p.curTok.Type == TokenNationalString {
8817+
strLit, _ := p.parseStringLiteral()
8818+
kind := ""
8819+
switch optName {
8820+
case "SUBJECT":
8821+
kind = "Subject"
8822+
case "START_DATE":
8823+
kind = "StartDate"
8824+
case "EXPIRY_DATE":
8825+
kind = "ExpiryDate"
8826+
}
8827+
stmt.CertificateOptions = append(stmt.CertificateOptions, &ast.CertificateOption{
8828+
Kind: kind,
8829+
Value: strLit,
8830+
})
8831+
}
8832+
if p.curTok.Type == TokenComma {
8833+
p.nextToken()
8834+
} else {
8835+
break
8836+
}
8837+
}
8838+
}
8839+
}
8840+
8841+
// Optional ACTIVE FOR BEGIN_DIALOG
8842+
if strings.ToUpper(p.curTok.Literal) == "ACTIVE" {
8843+
p.nextToken() // consume ACTIVE
8844+
if strings.ToUpper(p.curTok.Literal) == "FOR" {
8845+
p.nextToken() // consume FOR
8846+
}
8847+
if strings.ToUpper(p.curTok.Literal) == "BEGIN_DIALOG" {
8848+
p.nextToken() // consume BEGIN_DIALOG
8849+
if p.curTok.Type == TokenEquals {
8850+
p.nextToken()
8851+
}
8852+
if strings.ToUpper(p.curTok.Literal) == "ON" {
8853+
stmt.ActiveForBeginDialog = "On"
8854+
p.nextToken()
8855+
} else if strings.ToUpper(p.curTok.Literal) == "OFF" {
8856+
stmt.ActiveForBeginDialog = "Off"
8857+
p.nextToken()
8858+
}
8859+
}
8860+
}
8861+
8862+
// Skip optional semicolon
8863+
if p.curTok.Type == TokenSemicolon {
8864+
p.nextToken()
86808865
}
86818866

8682-
// Skip rest of statement
8683-
p.skipToEndOfStatement()
86848867
return stmt, nil
86858868
}
86868869

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"todo": true}
1+
{}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"todo": true}
1+
{}

0 commit comments

Comments
 (0)