diff mbox series

[meta-swupdate,2/3] feat(signatures): add rootfs signatures to swupdate

Message ID 20220217183124.303291-2-adrian.freihofer@siemens.com
State Changes Requested
Headers show
Series [meta-swupdate,1/3] swupdate-image: remove invalid bbclass in inherit | expand

Commit Message

Adrian Freihofer Feb. 17, 2022, 6:31 p.m. UTC
From: Walter Schweizer <walter.schweizer@siemens.com>

This feature allows to verify a root file system before mounting
it readonly with a signature that is in a environment variable in
U-Boot. The verification is done in an initramfs phase.
The signature is base64 encoded.

The mechanism to add the signature is similar to adding a sha256
hash. Instead of "@" the "?" is used in sw-description.

	bootenv: (
            {
                name = "rootfs_sig_b";
                value = "?@@ROOTFS_IMAGE@@";
            }
        );

Signed-off-by: Walter Schweizer <walter.schweizer@siemens.com>
---
 classes/swupdate-common.bbclass | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

Comments

Stefano Babic Feb. 18, 2022, 10:12 a.m. UTC | #1
Hi Adrian, Walter,

On 17.02.22 19:31, Adrian Freihofer wrote:
> From: Walter Schweizer <walter.schweizer@siemens.com>
> 
> This feature allows to verify a root file system before mounting
> it readonly with a signature that is in a environment variable in
> U-Boot. The verification is done in an initramfs phase.
> The signature is base64 encoded.
> 
> The mechanism to add the signature is similar to adding a sha256
> hash. Instead of "@" the "?" is used in sw-description.
> 
> 	bootenv: (
>              {
>                  name = "rootfs_sig_b";
>                  value = "?@@ROOTFS_IMAGE@@";
>              }
>          );
> 

I think I understand the reason and I want to check with you if this is 
a general and secure solution to be mainlined. The use case should be 
secure boot, and rootfs should not be mounted if hash does not match.

However, it looks to me that using a U-Boot variable is a weak solution. 
The variable can easy be set, and there is no cryptographic check around 
U-Boot environment. If an attacker dumps the environment from flash and 
replaces with hiw own, he could easy get rid of this.

Because I think the use case here is Secure Boot, I just explain my way 
to do I used in previous projects. With secure boot, the BootROM should 
already have verified the bootloader and we can trust it. Instead of 
putting the hash into the environment, the hash can be added to the 
fitImage with the kernel. The big advantage is the fitImage is verified 
by U-Boot (how this is done, it depends on the SOC, in worst case U-Boot 
verifies in SW with a public key) and we can trust the fitImage, but we 
cannot trust the environment.

Even setting CONFIG_ENV_FLAGS_LIST_STATIC does not help a lot, because 
we are not able anymore to update.

What do you think ?

Best regards,
Stefano Babic

> Signed-off-by: Walter Schweizer <walter.schweizer@siemens.com>
> ---
>   classes/swupdate-common.bbclass | 29 +++++++++++++++++++++++++++++
>   1 file changed, 29 insertions(+)
> 
> diff --git a/classes/swupdate-common.bbclass b/classes/swupdate-common.bbclass
> index d77d9d4..9bcaf01 100644
> --- a/classes/swupdate-common.bbclass
> +++ b/classes/swupdate-common.bbclass
> @@ -51,6 +51,17 @@ def swupdate_get_sha256(s, filename):
>               m.update(data)
>       return m.hexdigest()
>   
> +def swupdate_get_sig(d, s, filename):
> +    import subprocess
> +
> +    keyfile = d.getVar('SWUPDATE_PRIVATE_KEY')
> +    passphrase = d.getVar('SWUPDATE_PASSWORD_FILE')
> +    signcmd = "zcat %s | openssl dgst -sha256 -keyform PEM -sign %s -passin file:%s | base64 -w 0" % \
> +        (os.path.join(s, filename), keyfile, passphrase)
> +    bb.note("running command %s" % (signcmd))
> +    signature = subprocess.run(signcmd, shell=True, check=True, capture_output=True)
> +    return signature.stdout.decode('ascii')
> +
>   def swupdate_extract_keys(keyfile_path):
>       try:
>           with open(keyfile_path, 'r') as f:
> @@ -93,6 +104,23 @@ def swupdate_write_sha256(s):
>           for line in write_lines:
>               f.write(line)
>   
> +def swupdate_write_sig(d, s):
> +    import re
> +    write_lines = []
> +    with open(os.path.join(s, "sw-description"), 'r') as f:
> +       for line in f:
> +          m = re.match(r"^\s+value\s+[=:]\s*(?P<quote>[\'\"])[?](?P<filename>.*)(?P=quote)", line)
> +          if m:
> +              filename = m.group('filename')
> +              hash = swupdate_get_sig(d, s, filename)
> +              write_lines.append(line.replace("?%s" % (filename), hash))
> +          else:
> +              write_lines.append(line)
> +
> +    with open(os.path.join(s, "sw-description"), 'w+') as f:
> +        for line in write_lines:
> +            f.write(line)
> +
>   def swupdate_expand_bitbake_variables(d, s):
>       write_lines = []
>   
> @@ -236,6 +264,7 @@ def prepare_sw_description(d):
>       swupdate_expand_auto_versions(d, s)
>   
>       swupdate_write_sha256(s)
> +    swupdate_write_sig(d, s)
>   
>       encrypt = d.getVar('SWUPDATE_ENCRYPT_SWDESC', True)
>       if encrypt:
Schweizer, Walter Feb. 18, 2022, 1:28 p.m. UTC | #2
Hi Stefano,

Yes, the mechanism is for secure-boot.

The boot-container including U-Boot is verified by a signature. We use
i.MX8 AHAB for this.

Then we load the  FIT image which *is* part of the rootfs. U-Boot
verifies the configuration and starts the kernel with an initramfs. So
both are verified by U-Boot. The initramfs then uses the signature in
the variable to verify the rootfs. So if the variable is not correctly
set, the verification will fail and initramfs will print an error and
stop. We do not need to trust the variable, we check.

Because the FIT image resides within the rootfs, we cannot store the
rootfs signature in it. That is why we selected the solution with the
variable. The variable is not just a hash - it is a signature and the
initramfs has the public-key.

Thanks
Walter

On Fri, 2022-02-18 at 11:12 +0100, Stefano Babic wrote:
> Hi Adrian, Walter,
> 
> On 17.02.22 19:31, Adrian Freihofer wrote:
> > From: Walter Schweizer <walter.schweizer@siemens.com>
> > 
> > This feature allows to verify a root file system before mounting
> > it readonly with a signature that is in a environment variable in
> > U-Boot. The verification is done in an initramfs phase.
> > The signature is base64 encoded.
> > 
> > The mechanism to add the signature is similar to adding a sha256
> > hash. Instead of "@" the "?" is used in sw-description.
> > 
> >         bootenv: (
> >              {
> >                  name = "rootfs_sig_b";
> >                  value = "?@@ROOTFS_IMAGE@@";
> >              }
> >          );
> > 
> 
> I think I understand the reason and I want to check with you if this
> is 
> a general and secure solution to be mainlined. The use case should be
> secure boot, and rootfs should not be mounted if hash does not match.
> 
> However, it looks to me that using a U-Boot variable is a weak
> solution. 
> The variable can easy be set, and there is no cryptographic check
> around 
> U-Boot environment. If an attacker dumps the environment from flash
> and 
> replaces with hiw own, he could easy get rid of this.
> 
> Because I think the use case here is Secure Boot, I just explain my
> way 
> to do I used in previous projects. With secure boot, the BootROM
> should 
> already have verified the bootloader and we can trust it. Instead of 
> putting the hash into the environment, the hash can be added to the 
> fitImage with the kernel. The big advantage is the fitImage is
> verified 
> by U-Boot (how this is done, it depends on the SOC, in worst case U-
> Boot 
> verifies in SW with a public key) and we can trust the fitImage, but
> we 
> cannot trust the environment.
> 
> Even setting CONFIG_ENV_FLAGS_LIST_STATIC does not help a lot,
> because 
> we are not able anymore to update.
> 
> What do you think ?
> 
> Best regards,
> Stefano Babic
> 
> > Signed-off-by: Walter Schweizer <walter.schweizer@siemens.com>
> > ---
> >   classes/swupdate-common.bbclass | 29
> > +++++++++++++++++++++++++++++
> >   1 file changed, 29 insertions(+)
> > 
> > diff --git a/classes/swupdate-common.bbclass b/classes/swupdate-
> > common.bbclass
> > index d77d9d4..9bcaf01 100644
> > --- a/classes/swupdate-common.bbclass
> > +++ b/classes/swupdate-common.bbclass
> > @@ -51,6 +51,17 @@ def swupdate_get_sha256(s, filename):
> >               m.update(data)
> >       return m.hexdigest()
> >   
> > +def swupdate_get_sig(d, s, filename):
> > +    import subprocess
> > +
> > +    keyfile = d.getVar('SWUPDATE_PRIVATE_KEY')
> > +    passphrase = d.getVar('SWUPDATE_PASSWORD_FILE')
> > +    signcmd = "zcat %s | openssl dgst -sha256 -keyform PEM -sign
> > %s -passin file:%s | base64 -w 0" % \
> > +        (os.path.join(s, filename), keyfile, passphrase)
> > +    bb.note("running command %s" % (signcmd))
> > +    signature = subprocess.run(signcmd, shell=True, check=True,
> > capture_output=True)
> > +    return signature.stdout.decode('ascii')
> > +
> >   def swupdate_extract_keys(keyfile_path):
> >       try:
> >           with open(keyfile_path, 'r') as f:
> > @@ -93,6 +104,23 @@ def swupdate_write_sha256(s):
> >           for line in write_lines:
> >               f.write(line)
> >   
> > +def swupdate_write_sig(d, s):
> > +    import re
> > +    write_lines = []
> > +    with open(os.path.join(s, "sw-description"), 'r') as f:
> > +       for line in f:
> > +          m =
> > re.match(r"^\s+value\s+[=:]\s*(?P<quote>[\'\"])[?](?P<filename>.*)(
> > ?P=quote)", line)
> > +          if m:
> > +              filename = m.group('filename')
> > +              hash = swupdate_get_sig(d, s, filename)
> > +              write_lines.append(line.replace("?%s" % (filename),
> > hash))
> > +          else:
> > +              write_lines.append(line)
> > +
> > +    with open(os.path.join(s, "sw-description"), 'w+') as f:
> > +        for line in write_lines:
> > +            f.write(line)
> > +
> >   def swupdate_expand_bitbake_variables(d, s):
> >       write_lines = []
> >   
> > @@ -236,6 +264,7 @@ def prepare_sw_description(d):
> >       swupdate_expand_auto_versions(d, s)
> >   
> >       swupdate_write_sha256(s)
> > +    swupdate_write_sig(d, s)
> >   
> >       encrypt = d.getVar('SWUPDATE_ENCRYPT_SWDESC', True)
> >       if encrypt:
> 
>
Stefano Babic Feb. 18, 2022, 2:51 p.m. UTC | #3
Hi Walter,

On 18.02.22 14:28, Schweizer, Walter wrote:
> Hi Stefano,
> 
> Yes, the mechanism is for secure-boot.
> 
> The boot-container including U-Boot is verified by a signature. We use
> i.MX8 AHAB for this.
> 

Ok, fine.

> Then we load the  FIT image which *is* part of the rootfs. U-Boot
> verifies the configuration and starts the kernel with an initramfs.

Got it  so U-Boot is verified by AHAB. rootfs is at the moment 
untrusted, you load a fitImage ==> this is still ok IMHO. But why is 
fitImage not verified by U-Boot, independently if this is done by AHAB 
or by U-Boot in code ? Or is it verified by AHAB ?

> So
> both are verified by U-Boot.

Ok - so you mean that fitImage is verified by U-Boot, right ?

> The initramfs then uses the signature in
> the variable to verify the rootfs.

So you are verifying rootfs with a signature (urrg...then we have a 
256bytes long variable in U-Boot) via openSSL.

> So if the variable is not correctly
> set, the verification will fail and initramfs will print an error and
> stop. We do not need to trust the variable, we check.

Ok - my concern is if an attacker can work around ths, but this is 
possible only if the fitImage is not authenticated.

> 
> Because the FIT image resides within the rootfs, we cannot store the
> rootfs signature in it.

Ok, this is the point (chicken-egg problem).

> That is why we selected the solution with the
> variable. The variable is not just a hash - it is a signature and the
> initramfs has the public-key.

Ok, got it

Regards,
Stefano

> 
> Thanks
> Walter
> 
> On Fri, 2022-02-18 at 11:12 +0100, Stefano Babic wrote:
>> Hi Adrian, Walter,
>>
>> On 17.02.22 19:31, Adrian Freihofer wrote:
>>> From: Walter Schweizer <walter.schweizer@siemens.com>
>>>
>>> This feature allows to verify a root file system before mounting
>>> it readonly with a signature that is in a environment variable in
>>> U-Boot. The verification is done in an initramfs phase.
>>> The signature is base64 encoded.
>>>
>>> The mechanism to add the signature is similar to adding a sha256
>>> hash. Instead of "@" the "?" is used in sw-description.
>>>
>>>          bootenv: (
>>>               {
>>>                   name = "rootfs_sig_b";
>>>                   value = "?@@ROOTFS_IMAGE@@";
>>>               }
>>>           );
>>>
>>
>> I think I understand the reason and I want to check with you if this
>> is
>> a general and secure solution to be mainlined. The use case should be
>> secure boot, and rootfs should not be mounted if hash does not match.
>>
>> However, it looks to me that using a U-Boot variable is a weak
>> solution.
>> The variable can easy be set, and there is no cryptographic check
>> around
>> U-Boot environment. If an attacker dumps the environment from flash
>> and
>> replaces with hiw own, he could easy get rid of this.
>>
>> Because I think the use case here is Secure Boot, I just explain my
>> way
>> to do I used in previous projects. With secure boot, the BootROM
>> should
>> already have verified the bootloader and we can trust it. Instead of
>> putting the hash into the environment, the hash can be added to the
>> fitImage with the kernel. The big advantage is the fitImage is
>> verified
>> by U-Boot (how this is done, it depends on the SOC, in worst case U-
>> Boot
>> verifies in SW with a public key) and we can trust the fitImage, but
>> we
>> cannot trust the environment.
>>
>> Even setting CONFIG_ENV_FLAGS_LIST_STATIC does not help a lot,
>> because
>> we are not able anymore to update.
>>
>> What do you think ?
>>
>> Best regards,
>> Stefano Babic
>>
>>> Signed-off-by: Walter Schweizer <walter.schweizer@siemens.com>
>>> ---
>>>    classes/swupdate-common.bbclass | 29
>>> +++++++++++++++++++++++++++++
>>>    1 file changed, 29 insertions(+)
>>>
>>> diff --git a/classes/swupdate-common.bbclass b/classes/swupdate-
>>> common.bbclass
>>> index d77d9d4..9bcaf01 100644
>>> --- a/classes/swupdate-common.bbclass
>>> +++ b/classes/swupdate-common.bbclass
>>> @@ -51,6 +51,17 @@ def swupdate_get_sha256(s, filename):
>>>                m.update(data)
>>>        return m.hexdigest()
>>>    
>>> +def swupdate_get_sig(d, s, filename):
>>> +    import subprocess
>>> +
>>> +    keyfile = d.getVar('SWUPDATE_PRIVATE_KEY')
>>> +    passphrase = d.getVar('SWUPDATE_PASSWORD_FILE')
>>> +    signcmd = "zcat %s | openssl dgst -sha256 -keyform PEM -sign
>>> %s -passin file:%s | base64 -w 0" % \
>>> +        (os.path.join(s, filename), keyfile, passphrase)
>>> +    bb.note("running command %s" % (signcmd))
>>> +    signature = subprocess.run(signcmd, shell=True, check=True,
>>> capture_output=True)
>>> +    return signature.stdout.decode('ascii')
>>> +
>>>    def swupdate_extract_keys(keyfile_path):
>>>        try:
>>>            with open(keyfile_path, 'r') as f:
>>> @@ -93,6 +104,23 @@ def swupdate_write_sha256(s):
>>>            for line in write_lines:
>>>                f.write(line)
>>>    
>>> +def swupdate_write_sig(d, s):
>>> +    import re
>>> +    write_lines = []
>>> +    with open(os.path.join(s, "sw-description"), 'r') as f:
>>> +       for line in f:
>>> +          m =
>>> re.match(r"^\s+value\s+[=:]\s*(?P<quote>[\'\"])[?](?P<filename>.*)(
>>> ?P=quote)", line)
>>> +          if m:
>>> +              filename = m.group('filename')
>>> +              hash = swupdate_get_sig(d, s, filename)
>>> +              write_lines.append(line.replace("?%s" % (filename),
>>> hash))
>>> +          else:
>>> +              write_lines.append(line)
>>> +
>>> +    with open(os.path.join(s, "sw-description"), 'w+') as f:
>>> +        for line in write_lines:
>>> +            f.write(line)
>>> +
>>>    def swupdate_expand_bitbake_variables(d, s):
>>>        write_lines = []
>>>    
>>> @@ -236,6 +264,7 @@ def prepare_sw_description(d):
>>>        swupdate_expand_auto_versions(d, s)
>>>    
>>>        swupdate_write_sha256(s)
>>> +    swupdate_write_sig(d, s)
>>>    
>>>        encrypt = d.getVar('SWUPDATE_ENCRYPT_SWDESC', True)
>>>        if encrypt:
>>
>>
>
Schweizer, Walter Feb. 18, 2022, 4 p.m. UTC | #4
Hi Stefano

Thanks for your reply.
See below.

Best Regards
Walter

On Fri, 2022-02-18 at 15:51 +0100, Stefano Babic wrote:
> Hi Walter,
> 
> On 18.02.22 14:28, Schweizer, Walter wrote:
> > Hi Stefano,
> > 
> > Yes, the mechanism is for secure-boot.
> > 
> > The boot-container including U-Boot is verified by a signature. We
> > use
> > i.MX8 AHAB for this.
> > 
> 
> Ok, fine.
> 
> > Then we load the  FIT image which *is* part of the rootfs. U-Boot
> > verifies the configuration and starts the kernel with an initramfs.
> 
> Got it  so U-Boot is verified by AHAB. rootfs is at the moment 
> untrusted, you load a fitImage ==> this is still ok IMHO. But why is 
> fitImage not verified by U-Boot, independently if this is done by
> AHAB 
> or by U-Boot in code ? Or is it verified by AHAB ?
> 
> > So
> > both are verified by U-Boot.
> 
> Ok - so you mean that fitImage is verified by U-Boot, right ?
Yes, the fitImage is verified by U-Boot. Therefore the kernel and the
bundled initramfs are verified.

> 
> > The initramfs then uses the signature in
> > the variable to verify the rootfs.
> 
> So you are verifying rootfs with a signature (urrg...then we have a 
> 256bytes long variable in U-Boot) via openSSL.
> 
> 
Yes. Works so far well.

> > So if the variable is not correctly
> > set, the verification will fail and initramfs will print an error
> > and
> > stop. We do not need to trust the variable, we check.
> 
> Ok - my concern is if an attacker can work around ths, but this is 
> possible only if the fitImage is not authenticated.
> 
I turned off legacy image support.

> > 
> > Because the FIT image resides within the rootfs, we cannot store
> > the
> > rootfs signature in it.
> 
> Ok, this is the point (chicken-egg problem).
Yes, exactly.

> 
> > That is why we selected the solution with the
> > variable. The variable is not just a hash - it is a signature and
> > the
> > initramfs has the public-key.
> 
> Ok, got it
> 
> Regards,
> Stefano
> 
> > 
> > Thanks
> > Walter
> > 
> > On Fri, 2022-02-18 at 11:12 +0100, Stefano Babic wrote:
> > > Hi Adrian, Walter,
> > > 
> > > On 17.02.22 19:31, Adrian Freihofer wrote:
> > > > From: Walter Schweizer <walter.schweizer@siemens.com>
> > > > 
> > > > This feature allows to verify a root file system before
> > > > mounting
> > > > it readonly with a signature that is in a environment variable
> > > > in
> > > > U-Boot. The verification is done in an initramfs phase.
> > > > The signature is base64 encoded.
> > > > 
> > > > The mechanism to add the signature is similar to adding a
> > > > sha256
> > > > hash. Instead of "@" the "?" is used in sw-description.
> > > > 
> > > >          bootenv: (
> > > >               {
> > > >                   name = "rootfs_sig_b";
> > > >                   value = "?@@ROOTFS_IMAGE@@";
> > > >               }
> > > >           );
> > > > 
> > > 
> > > I think I understand the reason and I want to check with you if
> > > this
> > > is
> > > a general and secure solution to be mainlined. The use case
> > > should be
> > > secure boot, and rootfs should not be mounted if hash does not
> > > match.
> > > 
> > > However, it looks to me that using a U-Boot variable is a weak
> > > solution.
> > > The variable can easy be set, and there is no cryptographic check
> > > around
> > > U-Boot environment. If an attacker dumps the environment from
> > > flash
> > > and
> > > replaces with hiw own, he could easy get rid of this.
> > > 
> > > Because I think the use case here is Secure Boot, I just explain
> > > my
> > > way
> > > to do I used in previous projects. With secure boot, the BootROM
> > > should
> > > already have verified the bootloader and we can trust it. Instead
> > > of
> > > putting the hash into the environment, the hash can be added to
> > > the
> > > fitImage with the kernel. The big advantage is the fitImage is
> > > verified
> > > by U-Boot (how this is done, it depends on the SOC, in worst case
> > > U-
> > > Boot
> > > verifies in SW with a public key) and we can trust the fitImage,
> > > but
> > > we
> > > cannot trust the environment.
> > > 
> > > Even setting CONFIG_ENV_FLAGS_LIST_STATIC does not help a lot,
> > > because
> > > we are not able anymore to update.
> > > 
> > > What do you think ?
> > > 
> > > Best regards,
> > > Stefano Babic
> > > 
> > > > Signed-off-by: Walter Schweizer <walter.schweizer@siemens.com>
> > > > ---
> > > >    classes/swupdate-common.bbclass | 29
> > > > +++++++++++++++++++++++++++++
> > > >    1 file changed, 29 insertions(+)
> > > > 
> > > > diff --git a/classes/swupdate-common.bbclass
> > > > b/classes/swupdate-
> > > > common.bbclass
> > > > index d77d9d4..9bcaf01 100644
> > > > --- a/classes/swupdate-common.bbclass
> > > > +++ b/classes/swupdate-common.bbclass
> > > > @@ -51,6 +51,17 @@ def swupdate_get_sha256(s, filename):
> > > >                m.update(data)
> > > >        return m.hexdigest()
> > > >    
> > > > +def swupdate_get_sig(d, s, filename):
> > > > +    import subprocess
> > > > +
> > > > +    keyfile = d.getVar('SWUPDATE_PRIVATE_KEY')
> > > > +    passphrase = d.getVar('SWUPDATE_PASSWORD_FILE')
> > > > +    signcmd = "zcat %s | openssl dgst -sha256 -keyform PEM -
> > > > sign
> > > > %s -passin file:%s | base64 -w 0" % \
> > > > +        (os.path.join(s, filename), keyfile, passphrase)
> > > > +    bb.note("running command %s" % (signcmd))
> > > > +    signature = subprocess.run(signcmd, shell=True,
> > > > check=True,
> > > > capture_output=True)
> > > > +    return signature.stdout.decode('ascii')
> > > > +
> > > >    def swupdate_extract_keys(keyfile_path):
> > > >        try:
> > > >            with open(keyfile_path, 'r') as f:
> > > > @@ -93,6 +104,23 @@ def swupdate_write_sha256(s):
> > > >            for line in write_lines:
> > > >                f.write(line)
> > > >    
> > > > +def swupdate_write_sig(d, s):
> > > > +    import re
> > > > +    write_lines = []
> > > > +    with open(os.path.join(s, "sw-description"), 'r') as f:
> > > > +       for line in f:
> > > > +          m =
> > > > re.match(r"^\s+value\s+[=:]\s*(?P<quote>[\'\"])[?](?P<filename>
> > > > .*)(
> > > > ?P=quote)", line)
> > > > +          if m:
> > > > +              filename = m.group('filename')
> > > > +              hash = swupdate_get_sig(d, s, filename)
> > > > +              write_lines.append(line.replace("?%s" %
> > > > (filename),
> > > > hash))
> > > > +          else:
> > > > +              write_lines.append(line)
> > > > +
> > > > +    with open(os.path.join(s, "sw-description"), 'w+') as f:
> > > > +        for line in write_lines:
> > > > +            f.write(line)
> > > > +
> > > >    def swupdate_expand_bitbake_variables(d, s):
> > > >        write_lines = []
> > > >    
> > > > @@ -236,6 +264,7 @@ def prepare_sw_description(d):
> > > >        swupdate_expand_auto_versions(d, s)
> > > >    
> > > >        swupdate_write_sha256(s)
> > > > +    swupdate_write_sig(d, s)
> > > >    
> > > >        encrypt = d.getVar('SWUPDATE_ENCRYPT_SWDESC', True)
> > > >        if encrypt:
> > > 
> > > 
> > 
> 
>
Stefano Babic Feb. 20, 2022, 11:28 a.m. UTC | #5
Hi Walter,

On 17.02.22 19:31, Adrian Freihofer wrote:
> From: Walter Schweizer <walter.schweizer@siemens.com>
> 
> This feature allows to verify a root file system before mounting
> it readonly with a signature that is in a environment variable in
> U-Boot. The verification is done in an initramfs phase.
> The signature is base64 encoded.
> 
> The mechanism to add the signature is similar to adding a sha256
> hash. Instead of "@" the "?" is used in sw-description.
> 
> 	bootenv: (
>              {
>                  name = "rootfs_sig_b";
>                  value = "?@@ROOTFS_IMAGE@@";
>              }
>          );
> 

This just increase the chaos in swupdate-common, adding characters that 
should hide a special meaning. It is not far away for a previous post, 
see this thread, too:

https://groups.google.com/g/swupdate/c/cJtvPJUsIxU/m/P8ywiyn0FQAJ

Or the mainlined Auto version.

Even in that case, a special pattern was proposed. I am sure that this 
design does not scale at all, and more and more functions will be added 
in future, some with general purpose and some to solve a project 
specific issue. And we have already some special case like sha256, or 
@SWUPDATE_AUTO_VERSION. It is clear to me thaat something else should be 
done.

My approach is to have a wayto call functions defined in sw-description,
so that the change above could be written as:

  	bootenv: (
               {
                   name = "rootfs_sig_b";
                   value = "$swupdate_sign(@@ROOTFS_IMAGE@@)";
               }
           );

And we have just to check for "$" to exec a function, like we have in 
recipe for example with "$bb.utils...". The parsing won't be change in 
future and it is enough to add the functions, that can be collected 
together ($swupdate_het_sha256, $swupdate_size, $swupdate_ivt...).


> Signed-off-by: Walter Schweizer <walter.schweizer@siemens.com>
> ---
>   classes/swupdate-common.bbclass | 29 +++++++++++++++++++++++++++++
>   1 file changed, 29 insertions(+)
> 
> diff --git a/classes/swupdate-common.bbclass b/classes/swupdate-common.bbclass
> index d77d9d4..9bcaf01 100644
> --- a/classes/swupdate-common.bbclass
> +++ b/classes/swupdate-common.bbclass
> @@ -51,6 +51,17 @@ def swupdate_get_sha256(s, filename):
>               m.update(data)
>       return m.hexdigest()
>   
> +def swupdate_get_sig(d, s, filename):
> +    import subprocess
> +
> +    keyfile = d.getVar('SWUPDATE_PRIVATE_KEY')
> +    passphrase = d.getVar('SWUPDATE_PASSWORD_FILE')
> +    signcmd = "zcat %s | openssl dgst -sha256 -keyform PEM -sign %s -passin file:%s | base64 -w 0" % \
> +        (os.path.join(s, filename), keyfile, passphrase)

This does not work - we can have uncompressed artifact or zstd as 
compressor.

> +    bb.note("running command %s" % (signcmd))
> +    signature = subprocess.run(signcmd, shell=True, check=True, capture_output=True)
> +    return signature.stdout.decode('ascii')
> +
>   def swupdate_extract_keys(keyfile_path):
>       try:
>           with open(keyfile_path, 'r') as f:
> @@ -93,6 +104,23 @@ def swupdate_write_sha256(s):
>           for line in write_lines:
>               f.write(line)
>   
> +def swupdate_write_sig(d, s):
> +    import re
> +    write_lines = []
> +    with open(os.path.join(s, "sw-description"), 'r') as f:
> +       for line in f:
> +          m = re.match(r"^\s+value\s+[=:]\s*(?P<quote>[\'\"])[?](?P<filename>.*)(?P=quote)", line)
> +          if m:
> +              filename = m.group('filename')
> +              hash = swupdate_get_sig(d, s, filename)
> +              write_lines.append(line.replace("?%s" % (filename), hash))
> +          else:
> +              write_lines.append(line)
> +
> +    with open(os.path.join(s, "sw-description"), 'w+') as f:
> +        for line in write_lines:
> +            f.write(line)
> +

This is also a copy of already copied code in swupdate-common, and this 
tells us we need to refactor. sw-description is read / rewritten many 
times, and it is sure we can do better.

>   def swupdate_expand_bitbake_variables(d, s):
>       write_lines = []
>   
> @@ -236,6 +264,7 @@ def prepare_sw_description(d):
>       swupdate_expand_auto_versions(d, s)
>   
>       swupdate_write_sha256(s)
> +    swupdate_write_sig(d, s)
>   
>       encrypt = d.getVar('SWUPDATE_ENCRYPT_SWDESC', True)
>       if encrypt:

Best regards,
Stefano
Schweizer, Walter Feb. 25, 2022, 10:58 a.m. UTC | #6
Hi Stefano,

On Sun, 2022-02-20 at 12:28 +0100, Stefano Babic wrote:
> Hi Walter,
> 
> On 17.02.22 19:31, Adrian Freihofer wrote:
> > From: Walter Schweizer <walter.schweizer@siemens.com>
> > 
> > This feature allows to verify a root file system before mounting
> > it readonly with a signature that is in a environment variable in
> > U-Boot. The verification is done in an initramfs phase.
> > The signature is base64 encoded.
> > 
> > The mechanism to add the signature is similar to adding a sha256
> > hash. Instead of "@" the "?" is used in sw-description.
> > 
> >         bootenv: (
> >              {
> >                  name = "rootfs_sig_b";
> >                  value = "?@@ROOTFS_IMAGE@@";
> >              }
> >          );
> > 
> 
> This just increase the chaos in swupdate-common, adding characters
> that 
> should hide a special meaning. It is not far away for a previous
> post, 
> see this thread, too:
> 
> https://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgroups.google.com%2Fg%2Fswupdate%2Fc%2FcJtvPJUsIxU%2Fm%2FP8ywiyn0FQAJ&amp;data=04%7C01%7Cwalter.schweizer%40siemens.com%7C885aab6f44a5452c59cc08d9f4642137%7C38ae3bcd95794fd4addab42e1495d55a%7C1%7C0%7C637809533159236598%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=NbcxzOvkS6aB573GQnuZbxIHiKGoeBXdrqNhSE5K9YM%3D&amp;reserved=0
> 
> Or the mainlined Auto version.
> 
> Even in that case, a special pattern was proposed. I am sure that
> this 
> design does not scale at all, and more and more functions will be
> added 
> in future, some with general purpose and some to solve a project 
> specific issue. And we have already some special case like sha256, or
> @SWUPDATE_AUTO_VERSION. It is clear to me thaat something else should
> be 
> done.
> 
> My approach is to have a wayto call functions defined in sw-
> description,
> so that the change above could be written as:
> 
>         bootenv: (
>                {
>                    name = "rootfs_sig_b";
>                    value = "$swupdate_sign(@@ROOTFS_IMAGE@@)";
>                }
>            );
> 
> And we have just to check for "$" to exec a function, like we have in
> recipe for example with "$bb.utils...". The parsing won't be change
> in 
> future and it is enough to add the functions, that can be collected 
> together ($swupdate_het_sha256, $swupdate_size, $swupdate_ivt...).

I see your point.

> 
> 
> > Signed-off-by: Walter Schweizer <walter.schweizer@siemens.com>
> > ---
> >   classes/swupdate-common.bbclass | 29
> > +++++++++++++++++++++++++++++
> >   1 file changed, 29 insertions(+)
> > 
> > diff --git a/classes/swupdate-common.bbclass b/classes/swupdate-
> > common.bbclass
> > index d77d9d4..9bcaf01 100644
> > --- a/classes/swupdate-common.bbclass
> > +++ b/classes/swupdate-common.bbclass
> > @@ -51,6 +51,17 @@ def swupdate_get_sha256(s, filename):
> >               m.update(data)
> >       return m.hexdigest()
> >   
> > +def swupdate_get_sig(d, s, filename):
> > +    import subprocess
> > +
> > +    keyfile = d.getVar('SWUPDATE_PRIVATE_KEY')
> > +    passphrase = d.getVar('SWUPDATE_PASSWORD_FILE')
> > +    signcmd = "zcat %s | openssl dgst -sha256 -keyform PEM -sign
> > %s -passin file:%s | base64 -w 0" % \
> > +        (os.path.join(s, filename), keyfile, passphrase)
> 
> This does not work - we can have uncompressed artifact or zstd as 
> compressor.

Yes, I could distinguish between functions:
 $swupdate_sign()
 $swupdate_sign_zlib()
 $swupdate_sign_zstd()

The other possibility would be to remember the last compressed string
and deal in swupdate_sign(). 

I would not support for the boolean compressed variant.

> 
> > +    bb.note("running command %s" % (signcmd))
> > +    signature = subprocess.run(signcmd, shell=True, check=True,
> > capture_output=True)
> > +    return signature.stdout.decode('ascii')
> > +
> >   def swupdate_extract_keys(keyfile_path):
> >       try:
> >           with open(keyfile_path, 'r') as f:
> > @@ -93,6 +104,23 @@ def swupdate_write_sha256(s):
> >           for line in write_lines:
> >               f.write(line)
> >   
> > +def swupdate_write_sig(d, s):
> > +    import re
> > +    write_lines = []
> > +    with open(os.path.join(s, "sw-description"), 'r') as f:
> > +       for line in f:
> > +          m =
> > re.match(r"^\s+value\s+[=:]\s*(?P<quote>[\'\"])[?](?P<filename>.*)(
> > ?P=quote)", line)
> > +          if m:
> > +              filename = m.group('filename')
> > +              hash = swupdate_get_sig(d, s, filename)
> > +              write_lines.append(line.replace("?%s" % (filename),
> > hash))
> > +          else:
> > +              write_lines.append(line)
> > +
> > +    with open(os.path.join(s, "sw-description"), 'w+') as f:
> > +        for line in write_lines:
> > +            f.write(line)
> > +
> 
> This is also a copy of already copied code in swupdate-common, and
> this 
> tells us we need to refactor. sw-description is read / rewritten many
> times, and it is sure we can do better.

Agreed.

> 
> >   def swupdate_expand_bitbake_variables(d, s):
> >       write_lines = []
> >   
> > @@ -236,6 +264,7 @@ def prepare_sw_description(d):
> >       swupdate_expand_auto_versions(d, s)
> >   
> >       swupdate_write_sha256(s)
> > +    swupdate_write_sig(d, s)
> >   
> >       encrypt = d.getVar('SWUPDATE_ENCRYPT_SWDESC', True)
> >       if encrypt:
> 
> Best regards,
> Stefano
> 
Best Regards
Walter
Stefano Babic Feb. 25, 2022, 12:13 p.m. UTC | #7
Hi Walter,

On 25.02.22 11:58, Schweizer, Walter wrote:
> Hi Stefano,
> 
> On Sun, 2022-02-20 at 12:28 +0100, Stefano Babic wrote:
>> Hi Walter,
>>
>> On 17.02.22 19:31, Adrian Freihofer wrote:
>>> From: Walter Schweizer <walter.schweizer@siemens.com>
>>>
>>> This feature allows to verify a root file system before mounting
>>> it readonly with a signature that is in a environment variable in
>>> U-Boot. The verification is done in an initramfs phase.
>>> The signature is base64 encoded.
>>>
>>> The mechanism to add the signature is similar to adding a sha256
>>> hash. Instead of "@" the "?" is used in sw-description.
>>>
>>>          bootenv: (
>>>               {
>>>                   name = "rootfs_sig_b";
>>>                   value = "?@@ROOTFS_IMAGE@@";
>>>               }
>>>           );
>>>
>>
>> This just increase the chaos in swupdate-common, adding characters
>> that
>> should hide a special meaning. It is not far away for a previous
>> post,
>> see this thread, too:
>>
>> https://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgroups.google.com%2Fg%2Fswupdate%2Fc%2FcJtvPJUsIxU%2Fm%2FP8ywiyn0FQAJ&amp;data=04%7C01%7Cwalter.schweizer%40siemens.com%7C885aab6f44a5452c59cc08d9f4642137%7C38ae3bcd95794fd4addab42e1495d55a%7C1%7C0%7C637809533159236598%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=NbcxzOvkS6aB573GQnuZbxIHiKGoeBXdrqNhSE5K9YM%3D&amp;reserved=0
>>
>> Or the mainlined Auto version.
>>
>> Even in that case, a special pattern was proposed. I am sure that
>> this
>> design does not scale at all, and more and more functions will be
>> added
>> in future, some with general purpose and some to solve a project
>> specific issue. And we have already some special case like sha256, or
>> @SWUPDATE_AUTO_VERSION. It is clear to me thaat something else should
>> be
>> done.
>>
>> My approach is to have a wayto call functions defined in sw-
>> description,
>> so that the change above could be written as:
>>
>>          bootenv: (
>>                 {
>>                     name = "rootfs_sig_b";
>>                     value = "$swupdate_sign(@@ROOTFS_IMAGE@@)";
>>                 }
>>             );
>>
>> And we have just to check for "$" to exec a function, like we have in
>> recipe for example with "$bb.utils...". The parsing won't be change
>> in
>> future and it is enough to add the functions, that can be collected
>> together ($swupdate_het_sha256, $swupdate_size, $swupdate_ivt...).
> 
> I see your point.
> 
>>
>>
>>> Signed-off-by: Walter Schweizer <walter.schweizer@siemens.com>
>>> ---
>>>    classes/swupdate-common.bbclass | 29
>>> +++++++++++++++++++++++++++++
>>>    1 file changed, 29 insertions(+)
>>>
>>> diff --git a/classes/swupdate-common.bbclass b/classes/swupdate-
>>> common.bbclass
>>> index d77d9d4..9bcaf01 100644
>>> --- a/classes/swupdate-common.bbclass
>>> +++ b/classes/swupdate-common.bbclass
>>> @@ -51,6 +51,17 @@ def swupdate_get_sha256(s, filename):
>>>                m.update(data)
>>>        return m.hexdigest()
>>>    
>>> +def swupdate_get_sig(d, s, filename):
>>> +    import subprocess
>>> +
>>> +    keyfile = d.getVar('SWUPDATE_PRIVATE_KEY')
>>> +    passphrase = d.getVar('SWUPDATE_PASSWORD_FILE')
>>> +    signcmd = "zcat %s | openssl dgst -sha256 -keyform PEM -sign
>>> %s -passin file:%s | base64 -w 0" % \
>>> +        (os.path.join(s, filename), keyfile, passphrase)
>>
>> This does not work - we can have uncompressed artifact or zstd as
>> compressor.
> 
> Yes, I could distinguish between functions:
>   $swupdate_sign()
>   $swupdate_sign_zlib()
>   $swupdate_sign_zstd()
> 
> The other possibility would be to remember the last compressed string
> and deal in swupdate_sign().

But this does not work if the artifact was already compressed by the 
image recipe, let's say with IMAGE_FSTYPES:append = " zst".

Another way is to detect the type of compression, like running "file -L 
" on the artifact and detect which is the compression type (zst,zlib, none).

> 
> I would not support for the boolean compressed variant.
> 
>>
>>> +    bb.note("running command %s" % (signcmd))
>>> +    signature = subprocess.run(signcmd, shell=True, check=True,
>>> capture_output=True)
>>> +    return signature.stdout.decode('ascii')
>>> +
>>>    def swupdate_extract_keys(keyfile_path):
>>>        try:
>>>            with open(keyfile_path, 'r') as f:
>>> @@ -93,6 +104,23 @@ def swupdate_write_sha256(s):
>>>            for line in write_lines:
>>>                f.write(line)
>>>    
>>> +def swupdate_write_sig(d, s):
>>> +    import re
>>> +    write_lines = []
>>> +    with open(os.path.join(s, "sw-description"), 'r') as f:
>>> +       for line in f:
>>> +          m =
>>> re.match(r"^\s+value\s+[=:]\s*(?P<quote>[\'\"])[?](?P<filename>.*)(
>>> ?P=quote)", line)
>>> +          if m:
>>> +              filename = m.group('filename')
>>> +              hash = swupdate_get_sig(d, s, filename)
>>> +              write_lines.append(line.replace("?%s" % (filename),
>>> hash))
>>> +          else:
>>> +              write_lines.append(line)
>>> +
>>> +    with open(os.path.join(s, "sw-description"), 'w+') as f:
>>> +        for line in write_lines:
>>> +            f.write(line)
>>> +
>>
>> This is also a copy of already copied code in swupdate-common, and
>> this
>> tells us we need to refactor. sw-description is read / rewritten many
>> times, and it is sure we can do better.
> 
> Agreed.
> 
>>
>>>    def swupdate_expand_bitbake_variables(d, s):
>>>        write_lines = []
>>>    
>>> @@ -236,6 +264,7 @@ def prepare_sw_description(d):
>>>        swupdate_expand_auto_versions(d, s)
>>>    
>>>        swupdate_write_sha256(s)
>>> +    swupdate_write_sig(d, s)
>>>    
>>>        encrypt = d.getVar('SWUPDATE_ENCRYPT_SWDESC', True)
>>>        if encrypt:
>>
>> Best regards,
>> Stefano
>>
> Best Regards
> Walter
> 

Best regards,
Stefano
diff mbox series

Patch

diff --git a/classes/swupdate-common.bbclass b/classes/swupdate-common.bbclass
index d77d9d4..9bcaf01 100644
--- a/classes/swupdate-common.bbclass
+++ b/classes/swupdate-common.bbclass
@@ -51,6 +51,17 @@  def swupdate_get_sha256(s, filename):
             m.update(data)
     return m.hexdigest()
 
+def swupdate_get_sig(d, s, filename):
+    import subprocess
+
+    keyfile = d.getVar('SWUPDATE_PRIVATE_KEY')
+    passphrase = d.getVar('SWUPDATE_PASSWORD_FILE')
+    signcmd = "zcat %s | openssl dgst -sha256 -keyform PEM -sign %s -passin file:%s | base64 -w 0" % \
+        (os.path.join(s, filename), keyfile, passphrase)
+    bb.note("running command %s" % (signcmd))
+    signature = subprocess.run(signcmd, shell=True, check=True, capture_output=True)
+    return signature.stdout.decode('ascii')
+
 def swupdate_extract_keys(keyfile_path):
     try:
         with open(keyfile_path, 'r') as f:
@@ -93,6 +104,23 @@  def swupdate_write_sha256(s):
         for line in write_lines:
             f.write(line)
 
+def swupdate_write_sig(d, s):
+    import re
+    write_lines = []
+    with open(os.path.join(s, "sw-description"), 'r') as f:
+       for line in f:
+          m = re.match(r"^\s+value\s+[=:]\s*(?P<quote>[\'\"])[?](?P<filename>.*)(?P=quote)", line)
+          if m:
+              filename = m.group('filename')
+              hash = swupdate_get_sig(d, s, filename)
+              write_lines.append(line.replace("?%s" % (filename), hash))
+          else:
+              write_lines.append(line)
+
+    with open(os.path.join(s, "sw-description"), 'w+') as f:
+        for line in write_lines:
+            f.write(line)
+
 def swupdate_expand_bitbake_variables(d, s):
     write_lines = []
 
@@ -236,6 +264,7 @@  def prepare_sw_description(d):
     swupdate_expand_auto_versions(d, s)
 
     swupdate_write_sha256(s)
+    swupdate_write_sig(d, s)
 
     encrypt = d.getVar('SWUPDATE_ENCRYPT_SWDESC', True)
     if encrypt: